CMS 公共模块打包实践

背景

最近新搭建了两个 CMS (内容管理)系统,为了减小开发切换项目成本,和下降用户使用成本,两个系统使用了统一的脚手架 antd-pro。css

在功能开发的过程当中发现,两个系统之间存在不少相同的功能、逻辑。可能 A 项目写一下,后面 B 项目须要一样的实现又得再写一遍。前端

例如登陆页面的 UI、个性化 Table 组件封装、PageLoading 组件、请求封装等等,都是些 ts/tsx 文件,部分组件可能会依赖 less,jpeg/png/svg 等资源。node

无需多言,这样的存在确定是不合理的,如何避免 copy/paste?首先想到的把公共模块抽离出来发布一个 npm package。webpack

常见的打包方案

说到模块,首先想到的就是如何打包,常见的打包工具备一下几种:git

工具 优势 缺点
webpack/rollup 没有实现不了的 1. 配置麻烦(多个 entry/多个 output,还要保留组件路径)
typescript + babel 逻辑清晰,tsc 生成 d.ts + babel copy-files 能够很轻松把组件的 less、d.ts、js 文件生成到指定目录 只能处理 js、没法处理 less、静态资源
零配置打包工具 parcel、microbundle 零配置 场景固定,可配置项少,定制化成本极高

经过简单地对比,要定制化打包组件、还要打包 less/images,基本上只能选 webpack/rollup 了。github

打包成 commonjs 规范,编译 lesscss,生成 d.ts 文件。web

包目录树大概是这样:正则表达式

@scope/common
  - package.json
  - dist/
    - assets/
        - logo.jpg
    - components/
        - MyComponent/
            - index.css
            - index.js
            - index.d.ts
    
复制代码

到时候在项目中使用组件和 Antd 组件单独使用方式同样。手动引入 js 组件,再引入 css 文件typescript

import MyComponent1 from '@scope/common/dist/components/MyComponent1'
import '@scope/common/dist/components/MyComponent/index.css'
import MyComponent2 from '@scope/common/dist/components/MyComponent2'
import '@scope/common/dist/components/MyComponent2/index.css'
复制代码

常规操做,方案确定是可行的,可是可是对比抽离模块以前的方式,用起来真的很!不!方!便!原来的用法,直接引入组件就能够了,自动会将组件内部的其余依赖打包,简洁太多了。npm

import MyComponent1 from '../../components/MyComponent1'
import MyComponent2 from '../../components/MyComponent2'
复制代码

并且还有另外一个问题,本来组件内使用的图片资源怎么办?所有处理成 base64 吗?这样包体积会大幅增长。

非得打包不可吗?

回到最初的需求,抽离公共模块的目的是为了让组件、逻辑获得复用,避免一样的代码散落在各处。这部分代码只是咱们从两个管理系统抽离出来的公共代码,不具有全局通用性。

抽离后的用法最好也跟原来的方式同样,以 tsx? 文件为入口,内部的其余依赖都能被正确的 loader 处理。

大胆假设直接发布 typescript、less 代码,由实际使用方来决定如何打包。

固然能够了!,对于打包工具 webpack 来讲,须要打包的文件放在哪一个目录下还不是都同样。哪些文件使用哪些 loader,是经过 module.rule 中的 test, include, exclude 参数来配置的。

例如一下的配置,webpack 会让 my-project 下,非 node_modules 的代码都通过 babel-loader

module: {
    rules: [
      {
        test: /\.(js|mjs|jsx|ts|tsx)$/,
        include: [
          /my-project/
        ],
        exclude: [
          /node_modules/
        ],
        use: [
          ... babel-loader
        ]
      }
   ]
}
复制代码

umi(antd-pro 封装的框架) 使用的打包工具就是 webpack,从源码上看,umi@3.0.2 以上版本就会自动处理 node_modules 下的 Typescript

webpackConfig.module

    .rule('ts-in-node_modules')

      .test(/\.(jsx|ts|tsx)$/)

      .include.add(/node_modules/).end()

      .use('babel-loader')

        .loader(require.resolve('babel-loader'))

        .options(babelOpts);
复制代码

这里用的是 webpack-chian 的语法,它能够经过链式写法生成 webpack 配置。

打脸

本来文章到这里就结束了。当我满心欢喜开发时又出问题了,我将抽离出来的公共包,使用 npm link 到管理项目中后,报错了。

从报错上看,tsx 并无正确通过 babel-loader,why??? 仔细确认生成的 webpack 配置项以后,将目光锁定在了 webpack 自己。难道是软链引发的?

查阅文档,找到 webpack 软链相关的配置项 resolve.symlinks

Whether to resolve symlinks to their symlinked location. When enabled, symlinked resources are resolved to their real path, not their symlinked location. Note that this may cause module resolution to fail when using tools that symlink packages (like npm link).

默认状况下,webpack 会将软链解析成真实路径(关掉这个配置可能会致使其余问题)。因此问题是,使用 npm link 时,正则表达式 /node_modules/ test 匹配不到这个路径,致使最终公共模块的代码没有通过 babel-loader 处理。

这个报错在 npm publish 以后再 install 下来的状况是不会出现的。

问题明确了,剩下的就是修改 webpack 配置。

umi 如何修改 webpack 配置?

umi 经过 chainWebpack 项修改配置,用的仍是 webpack-chain 语法。这里只须要把公共模块的绝对路径加上就好了。

// config/config.ts


chainWebpack(memo) {
  memo.module
    .rule('ts-in-node_modules')
    .include.add(require('path').join(__dirname, '../../packages/'));
  return memo;
}
复制代码

Done, 如今能够无痛抽离公共模块了~ Happy Coding。

为了方便组件的使用,能够定义 index.tsx 来为作 reexport,而不是深刻到组件目录。

// index.tsx reexport
export { MyComponent } from './src/components/MyComponent'

// new usage
import { MyComponent } from '@scope/common'

vs

// old way
import MyComponent from '@scope/common/src/components/MyComponent'
复制代码

还须要修改 package.json,声明 maintypes

{
    main: './index.tsx',
    types: './index.tsx'
}
复制代码

最后一个问题,monorepo,不发包直接放在公共目录下能够吗?

项目中咱们使用 lerna 来管理多个前端项目,全部代码都在同一个文件夹下,若是不发包而是经过相对路径去引用公共文件夹下的代码能够吗?

- packages/
	- module-a/
	- module-b/
    - commons/

// 在 module-a 中直接经过路径访问
import PageLoading from '../../../commons/PageLoading'
复制代码

这种形式的问题是,module-a 代码范围超出了它自己应该负责的范畴。而且没有 npm 版本的概念,牵一发而动全身,容易出现【不肯定】的更新,还有分支依赖的问题。

相关文章
相关标签/搜索