本文主要是记录我在开发组件库时如何搭建环境(第二次架构升级)。
本次架构升级主要参考了element-plus
,包含以下几方面:
webpack
改成rollup
。rollup
配置要比webpack
简单的多了。rollup
天然支持tree sharking
,并且可以打包出es module
格式的文件,这样我们的 js 代码就可以天然支持es module
的tree sharking
功能lerna
进行项目管理。每个组件是一个子项目,这样每个组件就可以单独发布和使用,天然按需加载。mini-css-extract-plugin
将样式抽离出来了。但是这种方式并不适合多包架构,所以需要使用gulp
对样式进行单独打包webpack
提供的路径别名进行引用的,相对于相对路径,可以少写很多的../
,并且我们在打包一个组件的时候是不需要吧公共代码和其他组件的代码也打包进去,这样会造成代码的冗余。所以借助路径别名的另一个好处就是可以精准匹配我们引用的公共代码和组件,然后借助externals
字段排除掉这些依赖文件。升级到多包架构之后,公共代码和组件都是一个 npm 包,所以我们是通过依赖包的形式引入公共代码和组件的,在使用rollup
的时候直接读取package.json
中的dependencies
字段,通过external
字段忽略这些依赖包的打包。相比这下,使用 npm 包引入会比路径别名引入简单多了。npm i lerna -g
首先在项目的根目录初始化package.json
npm iniy -y
然后再初始化lerna
,我们才用的是固定模式来管理每个包
lerna init
这个时候你会看见一个lerna.json
文件。在文件中写入一下配置:
{
// 子项目所在的目录
"packages": [
"packages/*"
],
// 版本号
"version": "1.3.4",
"command": {
// lerna publish命令相关配置
"publish": {
// 忽略md文件的修改。如果某个子项目只修改了md文件,是不会被publish的
"ignoreChanges": [
"*.md"
],
// lerna publish 之后lerna会自动给我们提交代码,message就是提交代码的信息
"message": "chore(release): publish",
// 指定发布的地址,私服的话就填写私服的地址
"registry": "https://registry.npmjs.org"
}
}
}
当我们安装依赖的时候,node_modules
文件夹会出现在每一个子项目中,我们需要将他们提取到顶层目录中,避免相同依赖包安装多次。所以我们还需要修改一下我们的配置。
在lerna.json
文件中加入如下配置:
{
// 使用yarn安装依赖,没有安装yarn的需要安装一下
"npmClient": "yarn",
// 工作目录
"useWorkspaces": true,
}
在package.json
文件中加入如下字段:
{
// 声明为私包
"private": true,
// 工作目录
"workspaces": ["packages/*"]
}
经过上面的步骤,我们的 lerna 基本环境已经出来了,下面介绍几个常见的命令
安装所有子项目的依赖包:
lerna bootstrap
给每个项目添加依赖:
lerna add lodash
给指定的子项目添加依赖:
lerna add lodash --scope=@lin-view-ui/button
@lin-view-ui/button
指的是子项目中package.json
文件中的name
字段,并不是指文件夹
显示自上次 publish 之后,有修改过的子项目:
lerna changed
新建一个子项目,但是组件库这里基本用不上,因为生成出来的文件没有符合我的要求,所以需要使用 node 编写一个脚本,代替这条命令:
lerna add button
发布项目:
lerna publish
在发布之前,会让你挑选版本号。发布之后会自动提交你的代码,然后打tag
- project
- build // 打包构建
- packages
- button // button组件
- alert // alert组件
- types // 类型声明文件
- utils // 公共方法
- locale // i18n
- mixins // 公共mixins
- test-utils // 测试相关的方法
- theme-chalk // 样式
- lin-view-ui // 组件库总入口,全量包
- script // 脚本命令
这里我们的子项目分为 4 中类型,分别是组件,类型声明文件,样式,公共代码。
__test__
(编写测试用例的文件夹),src
(编写代码的地方),types
(类型声明文件),index.js
(打包的入口文件),package.json
,README.md
项目说明src
(编写类型声明文件),index.d.ts
(入口),package.json
,README.md
项目说明@lin-view-ui/utils
(工具类),@lin-view-ui/test-utils
(测试相关工具类),@lin-view-ui/mixins
(公共 mixins),@lin-view-ui/locale
(i18n 相关的东西),每个子项目包含src
(编写代码的地方),index.js
(打包的入口文件),package.json
,README.md
项目说明src
(编写样式的地方),package.json
,README.md
项目说明特别说明:
package.json
的private
字段为true
,所以每个子项目中的package.json
文件需要添加如下字段,否则会导致发包不成功{
"publishConfig": {
"access": "public"
}
}
package.json
文件必须包含如下字段:{
"typings": "types/index.d.ts",
"peerDependencies": {
"vue": "^2.6.14"
},
"dependencies": {
"@lin-view-ui/types": "^1.3.3"
}
}
由于我们的组件是依赖于 vue,所以需要预安装 vue。同时我们还需要类型声明文件来支持 ts。类型声明文件统一写在@lin-view-ui/types
子项目中。组件子项目依赖于这个子项目,然后在types/index.d.ts
文件中引入对应的组件类型声明,然后在导出。
package.json
文件包含的如下字段:{
// 包名
"name": "@lin-view-ui/tag",
// 版本号
"version": "1.3.4",
// 描述
"description": "tag",
// npm包入口文件
"main": "dist/index.js",
// 关键词
"keywords": [
"lin-view-ui",
"tag"
],
// 首页地址
"homepage": "https://github.com/c10342/lin-view-ui/tree/master/packages/tag",
// 仓库地址
"repository": {
"type": "git",
"url": "https://github.com/c10342/lin-view-ui"
},
// issues地址
"bugs": {
"url": "https://github.com/c10342/lin-view-ui/issues"
},
// 作者
"author": "c10342",
// 开源协议
"license": "MIT",
}
由于一个组件子项目包含了多个文件模板,测试文件模板,readme 文件模板,index.js 文件模板等等,这些如果是你自己手动去创建的话会很麻烦,所以我们借助 node 编写一个脚本。
node ./scripts/componentTemplate.js button
const argv = process.argv;
const componentName = argv[2]; // button
const path = require("path");
const packageRoot = path.resolve(__dirname, "../packages");
// 检查有没有输入组件名
if (!componentName) {
console.log(chalk.blueBright("请输入组件名"));
return;
}
const compomentPath = path.resolve(packageRoot, componentName);
// 检查输入的组件名是否已经存在了
if (fs.existsSync(compomentPath)) {
console.log(chalk.blueBright(`${componentName}组件已经存在了`));
return;
}
// 创建组件根目录
fs.mkdirSync(compomentPath);
// 创建src目录
const compomentSrcPath = path.resolve(compomentPath, "src");
fs.mkdirSync(compomentSrcPath);
// 创建__tests__目录
const testsSrcPath = path.resolve(compomentPath, "__tests__");
fs.mkdirSync(testsSrcPath);
// 创建types目录
const typesSrcPath = path.resolve(compomentPath, "types");
fs.mkdirSync(typesSrcPath);
下面以创建一个package.json
文件为例,其他文件模板创建是同样的道理。思路:读取模板->借助handlebars
填写模板数据->写入最终模板字符串到文件目录中
function writePakcageTpl() {
const parmas = {
name: componentName,
};
const tplStr = fs.readFileSync(
path.resolve(__dirname, "./template/package.tpl"),
"utf-8"
);
const result = handlebars.compile(tplStr)(parmas);
fs.writeFileSync(path.resolve(compomentPath, "package.json"), result);
}
writePakcageTpl();
读取每个子项目中的package.json
文件的dependencies
,peerDependencies
,devDependencies
,这些都是要在打包的时候排除的掉的依赖。
const root = path.resolve(__dirname, "../packages");
function getExternalsDep(name, dev = false) {
// 获取子项目根目录
const dir = path.resolve(root, name);
// 加载package.json
const pck = require(path.resolve(dir, "./package.json"));
const dependencies = pck.dependencies || {};
const peerDependencies = pck.peerDependencies || {};
const externals = [];
// 获取key值
Object.keys(dependencies).forEach((key) => externals.push(key));
Object.keys(peerDependencies).forEach((key) => externals.push(key));
if (dev) {
const devDependencies = pck.devDependencies || {};
Object.keys(devDependencies).forEach((key) => externals.push(key));
}
return [...new Set(externals), "flv.js/dist/flv.js"];
}
getExternalsDep("button");
安装依赖
npm i @rollup/plugin-node-resolve [email protected] @rollup/plugin-commonjs rollup del rollup-plugin-terser @rollup/plugin-image [email protected] vue-template-compiler -D
特别注意:rollup-plugin-vue 最新版已经出到6.0.0
了,但是6.0.0
这个版本不支持vue2
的打包,所以我们要回退到5.x
的版本。rollup-plugin-babel
最新版出到了5.x
了,但是我发现 vue 组件内部的箭头函数没有被转换,不知道为什么,回退到4.x
的版本箭头函数就可以被转换了
const { nodeResolve } = require("@rollup/plugin-node-resolve");
const babel = require("rollup-plugin-babel");
const commonjs = require("@rollup/plugin-commonjs");
const vue = require("rollup-plugin-vue");
const { terser } = require("rollup-plugin-terser");
const image = require("@rollup/plugin-image");
function createInputConfig(options = {}) {
const config = {
// 入口文件
input: options.input,
// 不需要进行打包的文件或者依赖
external: options.external,
plugins: [
// 识别node_modules目录
nodeResolve(),
// 处理vue文件
vue({}),
babel({
// 防止打包node_modules下的文件
exclude: "node_modules/**",
// 使plugin-transform-runtime生效
runtimeHelpers: true,
}),
// 将 CommonJS 模块转换为 ES6 的 Rollup 插件,rollup默认只支持 ES6+的模块方式
commonjs(),
// 将图片转成base64
image(),
],
};
// 压缩文件
if (options.minify) {
config.plugins.push(terser());
}
// 其他插件
if (options.plugins) {
config.plugins.unshift(...options.plugins);
}
return config;
}
注意:plugins
需要注意一下插件的顺序,特别是@rollup/plugin-commonjs
这个插件的顺序。
由于我们的配置中使用babel
所以,我们需要在项目的根目录中新建一个babel.config.js
文件,配置一下 babel 的一些东西
module.exports = {
presets: [
[
// 配置支持es的一些新特性
"@babel/preset-env",
{
// 按需引入需要用到的一些新特性语法
useBuiltIns: "usage",
// 使用core-js3的版本
corejs: 3,
targets: {
// 打包出来的文件最低需要支持到ie10
ie: "10",
},
},
],
],
plugins: [
// 避免全局变量污染
"@babel/plugin-transform-runtime",
// 处理vue的jsx语法
"@vue/babel-plugin-transform-vue-jsx",
],
};
/**
* @param {*} distPath 打包的输出路径
* @param {*} options 其他可选配置参数
*/
function createEsOutput(distPath, options = {}) {
return {
file: distPath,
format: "es",
...options,
};
}
// 打包出umd格式的文件
function createUmdOutput(distPath, options = {}) {
return {
file: distPath,
format: "umd",
...options,
};
}
安装依赖
npm i rollup -D
const rollup = require("rollup");
/**
* @param {*} inputOptions 输入配置 createInputConfig创建
* @param {*} outputOptions 输出配置 createEsOutput或者createUmdOutput创建
*/
async function rollupBuild(inputOptions, outputOptions) {
const bundle = await rollup.rollup(inputOptions);
await bundle.write(outputOptions);
}
安装依赖
npm i gulp gulp-sass gulp-clean-css gulp-rename gulp-autoprefixer gulp-concat node-sass sass -D
打包 css 包含 2 部分,一是将scss
编译为css
,二是拷贝字体图标文件,因为 gulp 是不会去处理字体图标文件,所以需要拷贝一份到输出目录
const { src, dest } = require("gulp");
// 编译scss
const sass = require("gulp-sass");
// 压缩样式文件
const cssmin = require("gulp-clean-css");
// 重命名文件名
const rename = require("gulp-rename");
// 样式添加前缀
const autoprefixer = require("gulp-autoprefixer");
// 将多个文件拼接成一个文件
const concat = require("gulp-concat");
const path = require("path");
// 样式存放的目录
const root = path.resolve(__dirname, "../packages/theme-chalk");
const resolve = (pathSrc) => path.resolve(root, pathSrc);
// 编译scss。srcPath:入口,可以是数组,最终会合并成一个文件;distPath:输出路径
const buildScss = (
srcPath,
distPath,
options = {
basename: "style",
}
) => {
return (
src(srcPath)
// 将多个样式文件拼接成一个
.pipe(concat("style.scss"))
// 编译scss
.pipe(sass().on("error", sass.logError))
// 给样式添加前缀
.pipe(autoprefixer({ cascade: false }))
// 压缩样式文件
.pipe(cssmin())
.pipe(
// 修改文件名
rename((srcPath) => {
srcPath.basename = options.basename;
// 后缀名
srcPath.extname = ".css";
})
)
// 输出到指定目录
.pipe(dest(distPath))
);
};
// 将字体图标文件拷贝到输出目录
function copyfont(distPath) {
return src(resolve("./src/fonts") + "/**")
.pipe(cssmin())
.pipe(dest(distPath));
}
删除指定目录
const del = require("del");
const clean = (cleanPath) => {
return del(cleanPath, {
force: true,
});
};
非组件子项目的子项目(白名单)
const whiteList = [
"locale",
"mixins",
"theme-chalk",
"utils",
"lin-view-ui",
"test-utils",
"types",
];
我们的公共代码主要有 4 个子项目,其中有三个需要进行打包的,分别是utils
,locale
,mixins
。下面以讲解打包utils
为例,其他 2 个子项目跟他是一样的
// 子项目根路径
const root = path.resolve(__dirname, "../packages/utils");
const resolve = (pathSrc) => {
return path.resolve(root, pathSrc);
};
// 创建输入配置
function createConfig(filename) {
let input = filename;
// 如果文件名不是index.js,路径需要加一个src
if (filename !== "index.js") {
input = `./src/${filename}`;
}
return createInputConfig({
input: resolve(input),
external: getExternalsDep("utils"),
});
}
// 打包单个文件的函数
const buildOne = async (filename) => {
const inputOptions = createConfig(filename);
const outputOptions = createEsOutput(resolve(`./dist/${filename}`));
await rollupBuild(inputOptions, outputOptions);
console.log(filename, "done");
};
// 获取所有需要进行打包的文件
const fileList = fs.readdirSync(resolve("./src"));
fileList.push("index.js");
// 打包整个utils目录的文件
const build = async () => {
// 先删除旧的输出目录
await clean(resolve("./dist"));
fileList.forEach((filename) => buildOne(filename));
};
build();
由于我们的组件都是放在packages
目录下面的,所以我们需要读取packages
目录下面的文件夹,然后过滤带那些非组件的文件夹。入口文件都是index.js
。组件打包完成之后,还需要吧组件对应的样式文件也打包一下
const root = path.resolve(__dirname, "../packages");
const resolve = (pathSrc) => path.resolve(root, pathSrc);
function createConfig(filename) {
return createInputConfig({
input: resolve(`./${filename}/index.js`),
external: getExternalsDep(filename),
});
}
const buildComponent = async (comp) => {
const inputConfig = createConfig(comp);
const outputConfig = createEsOutput(resolve(`./${comp}/dist/index.js`));
await clean(resolve(`./${comp}/dist`));
await rollupBuild(inputConfig, outputConfig);
// 打包样式,这里连同base基础样式也要一起打包进去
await buildScss(
[
resolve(`./theme-chalk/src/${comp}.scss`),
resolve(`./theme-chalk/src/base.scss`),
],
resolve(`./${comp}/dist`)
);
// 拷贝字体图标
await copyfont(resolve(`./${comp}/dist/fonts`));
console.log(comp, "done");
};
// 读取组件目录。并过滤非组件的目录
const compList = fs
.readdirSync(root)
.filter((fileName) => !whiteList.includes(fileName));
const build = () => {
compList.forEach((comp) => buildComponent(comp));
};
build();
打包出来的全量包需要支持按需加载功能,否则会增大开发者的 bundle 文件体积。这里我们借助babel-plugin-component
来实现我们的按需加载功能。我们打包出来的目录结构需要符合babel-plugin-component
的要求才能实现按需加载,目录格式如下:
- lib
- theme-chalk // 样式存放目录
- fonts // 字体图标文件
- button.css // button组件样式
- alert.css // alert组件样式
- base.css // 通用样式必须要有,但是也可以通过配置设置成非必须
- index.css // 总样式文件
- button.js // button组件
- alert.js // alert组件
- utils.js // 通用方法-公共代码
- locale.js // i18n-公共代码
- mixins // 公共逻辑-公共代码
- index.umd.js // umd格式的全量包
- index.js // npm包的模块入口,esmodule格式
首先我们要解决一些依赖包的引用问题。比如我引用了@lin-view-ui/button
这个组件,实际上我们可以通过lerna
进行软连接,链接到node_modules
中,这样就不用处理引用的问题了。但是有一个坏处,就是button
组件必须是已经经过打包的,并且生成了打包目录。因为 npm 包的查找规则是先查找package.json
中的main
字段所指向的文件,而我们的main
字段则是指向已经经过打包的文件。为了解决上述问题,我们需要借助@rollup/plugin-alias
这个插件来实现路径的改写,也就是路径别名。最终的路径别名映射到真实的路径是这样的:@lin-view-ui/button
->packages/button
,rollup 遇见packages/button
就会自动查找packages/button
下面的 index.js 文件。全量样式的话我们不需要在这里打包,因为后面还有一个打包出 esmodule 格式文件的步骤,我们在这一步中打包全量样式。
const root = path.resolve(__dirname, "../packages/lin-view-ui");
const packagesRoot = path.resolve(__dirname, "../packages");
const resolveInput = (pathSrc) => path.resolve(root, pathSrc);
const buildumdIndex = async () => {
const inputConfig = createInputConfig({
input: resolveInput("./index.js"),
// 全量包只需要跳过vue依赖的打包即可
external: ["vue"],
// 压缩文件
minify: true,
plugins: [
alias({
// 将@lin-view-ui映射到packages目录
entries: [{ find: /^@lin-view-ui/, replacement: packagesRoot }],
}),
],
});
const outputConfig = createUmdOutput(resolveOutput("./lib/index.umd.js"), {
// umd格式的文件需要包含一个name,作为全局变量
name: "LinViewUi",
globals: {
// 上面打包的时候跳过了vue,所以我们需要声明一下外部的vue变量是什么
vue: "Vue",
},
});
await rollupBuild(inputConfig, outputConfig);
console.log("umd build success");
};
其实也不能叫做全量包吧,因为从上面的目录中可以看见已经又一个button.js
的文件了,所以当我们遇见button
组件就可以改成引用./button.js
,这样就可以不用把button
组件打包进来了。所以我们这里也要修改一下依赖包的引用问题。比如改成这个样子:@lin-view-ui/button
->./button.js
,@lin-view-ui/mixins
->./mixins.js
。同时我们还要在这一步打包出一个全量样式文件,还有一个 base.css 样式文件。
const formatImportPath = (id) => {
// 修改依赖的引入方式,`@lin-view-ui/button`->`./button.js`
if (id.match(/^@lin-view-ui/)) {
const depName = id.split("/")[1];
return `./${depName}.js`;
}
};
const buildesIndex = async () => {
const inputConfig = createInputConfig({
input: resolveInput("./index.js"),
external: getExternalsDep("lin-view-ui", true),
plugins: [
alias({
entries: [{ find: /^@lin-view-ui/, replacement: packagesRoot }],
}),
],
});
const outputConfig = createEsOutput(resolveOutput("./lib/index.js"), {
// 这里,关键
paths: formatImportPath,
});
await rollupBuild(inputConfig, outputConfig);
// 打包全量样式
await buildScss(
resolveInput(`../theme-chalk/src/index.scss`),
resolveOutput(`./lib/theme-chalk`),
{
// 样式文件叫index.css
basename: "index",
}
);
// 打包base样式文件
await buildScss(
resolveInput(`../theme-chalk/src/base.scss`),
resolveOutput(`./lib/theme-chalk`),
{
basename: "base",
}
);
// 拷贝字体图标文件
await copyfont(resolveOutput(`./lib/theme-chalk/fonts`));
console.log("es build success");
};
同样也需要把依赖的引用路径进行转化一下,@lin-view-ui/button
->./button.js
。
const resolvePackage = (pathSrc) => path.resolve(packagesRoot, pathSrc);
const buildesComponent = async (comp) => {
const outputConfig = createEsOutput(resolveOutput(`./lib/${comp}.js`), {
paths: formatImportPath,
});
const inputConfig = createCompConfig(comp);
await rollupBuild(inputConfig, outputConfig);
// 打包组件样式
await buildScss(
resolvePackage(`./theme-chalk/src/${comp}.scss`),
resolveOutput(`./lib/theme-chalk`),
{
basename: comp,
}
);
console.log(comp, "done");
};
const compList = fs
.readdirSync(packagesRoot)
.filter((fileName) => !whiteList.includes(fileName));
compList.forEach((comp) => buildesComponent(comp));
公共代码包含utils
,mixins
,locale
,同样也需要把依赖的引用路径进行转化一下。以utils
为例
const buildesUtils = async () => {
const inputConfig = createInputConfig({
input: resolvePackage("./utils/index"),
external: getExternalsDep("utils"),
});
const outputConfig = createEsOutput(resolveOutput(`./lib/utils.js`), {
paths: formatImportPath,
});
await rollupBuild(inputConfig, outputConfig);
console.log("utils", "done");
};
buildesUtils();
其实当我们的配置编写完毕之后,也就是上面的创建输入配置
和创建输出配置
完成,打包工作就是指定入口文件和输出路径。从上面的打包组件,公共代码等步骤可以看出,其实就是在确定输入输出路径。而且配置起来也很简单,对比webpack
来说一个字就是爽。对于样式来说,其实也是确定输入输出路径,只是你要确定一下编译样式的时机(每个组件打包完成编译一下对应组件的样式,全量包编译完成的时候也需要编译全量样式)。
文档的打包也是需要路径别名来处理@lin-view-ui/button
这种引入依赖,转化关系是这样子的@lin-view-ui/button
->packages/button
。但是webpackage
比较有趣的是它的查找方式。packages/button
是一个文件夹,webpack
首先会先查找文件夹下面是否有package.json
文件,没有就直接获取文件夹下面名为的index.js
的文件,有则读取package.json
文件的main
字段,根据main
字段查找入口,上面已经说到了,main
字段是指向我们打包出来的目录dist/index.js
,但是我们想要让webpack
直接查找到我们的button/index.js
文件,所以我们需要修改一下webpack
的查找方式,我们需要借助resolve.mainFields
字段,让webpack
先根据我们规定的doc
字段查找文件,没有在根据main
字段查找文件。然后我们需要在每个子项目中的package.json
文件中添加doc
字段。关键代码如下:
webpack.docs.js
module.exports = {
resolve: {
// 先查找package.json文件的doc字段,在查找main字段
mainFields: ["doc", "main"],
},
};
packages/button/package.json
{
"doc": "index.js"
}
这样我们就可以在开发环境的时候直接修改代码,然后组件文档就能及时更新看到效果了,不用等待子项目 build 一次,再去刷新页面查看效果。
组件文档配置如下:
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const VueLoaderPlugin = require("vue-loader/lib/plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const output = path.resolve(__dirname, "../docs-dist");
let entry = path.resolve(__dirname, "../docs/main.js");
const isDev = process.env.NODE_ENV === "development";
const target = process.env.target;
if (target) {
entry = path.resolve(__dirname, `../${target}/main.js`);
}
const devConfig = {
mode: isDev ? "development" : "production",
performance: {
hints: false,
},
stats: {
modules: false,
children: false,
chunks: false,
chunkModules: false,
},
resolve: {
extensions: [".js", ".jsx", "md", ".vue", ".json"],
// 设置路径别名
alias: {
"@lin-view-ui": path.join(__dirname, "../packages"),
},
mainFields: ["doc", "main"],
},
entry,
output: {
path: output,
filename: isDev ? "js/[name].js" : "js/[name].[hash].js",
},
devtool: isDev ? "cheap-module-eval-source-map" : false,
devServer: {
open: true,
overlay: true, // 错误直接显示在浏览器中
contentBase: output,
hot: true,
historyApiFallback: true,
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: ["babel-loader"],
exclude: /node_modules/,
},
{
test: /\.vue$/,
loader: "vue-loader",
},
{
test: /\.md$/,
use: [
{
loader: "vue-loader",
},
{
loader: path.resolve(__dirname, "./md-loader/index.js"),
},
],
},
{
test: /\.css$/,
use: [
isDev ? "style-loader" : MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
importLoaders: 1,
},
},
"postcss-loader",
],
},
{
test: /\.scss$/,
use: [
isDev ? "style-loader" : MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
importLoaders: 2,
},
},
"postcss-loader",
"sass-loader",
],
},
{
test: /\.(png|jpg|jpeg|gif|eot|ttf|svg|woff|woff2)$/,
use: {
loader: "url-loader",
options: {
name: "[name].[hash].[ext]",
outputPath: "images/",
limit: 10240,
esModule: false,
},
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../docs/public/index.html"),
filename: "index.html",
favicon: path.resolve(__dirname, "../docs/public/favicon.ico"),
}),
new VueLoaderPlugin(),
],
};
if (!isDev) {
devConfig.plugins.push(new CleanWebpackPlugin());
devConfig.plugins.push(
new MiniCssExtractPlugin({
filename: "css/[name].[contenthash].css",
chunkFilename: "css/[name].[contenthash].chunk.css",
})
);
}
module.exports = devConfig;
本次架构升级最大的收获就是对rollup
和lerna
有了更深的了解。以及对多包架构模式也有了一定的了解。升级过程中,遇见了不少坑坑洼洼,还好最终都能解决掉。其中有不少的问题都是依赖包的版本问题引起的,比如rollup-plugin-babel
最新版本的包不能把vue组件的箭头函数转化为普通函数等等。最后,如果有兴趣一起交流学习的同学,欢迎私信我或者下方留言。同时,如果这篇文章能够帮助你,希望你能够给我点个赞。
github
文档地址