阅读 136

《打包系列|webpack配置篇》

欢迎访问我的博客

webpack 配置

初始化webpack

通过npm i -g webpack webpack-cli下载webpack和webpack的命令行包。

js 配置

我们都知道es6语法在低版本浏览器内是不会被识别的, 另外有一些语法不会被浏览器支持。于是就有了 babel这个工具链: 将 ECMAScript 2015+ (又可称为ES6ES7ES8等)版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中

Babel 主要做了如下三件事:

  • 语法转换
  • 通过 Polyfill 方式在目标环境中添加缺失的特性 (通过 @babel/polyfill 模块)
  • 源码转换, 比如 JSX 等

关于babel的相关插件和预设(.babelrc是babel的配置文件):

  • @babel/core:

babel的核心功能, 没有我下面的插件都别想用

  • @babel/preset-env:

提供一个预设环境(其实就是各种语法翻译包),是很多语法能够被转换成es5(箭头语法, let, const等) 但是有些es6后面的语法比如(includes等)是不会被翻译的. 另外babel的插件的使用都需要这个预设库 支持。

  • @babel/preset-react:

对react jsx等语法的翻译处理,另外可以使用js可选链操作

  • @babel/cli:

一个内置的 CLI 命令行工具,可通过命令行编译文件 比如:babel index.js -o ./dist/index.js

  • @babel/polyfill:

包括core-js和一个自定义的regenerator runtime模块. 解决了有些浏览器或者低版本NodeJs下(inclues, promise, async, from等)语法不支持的问题

  • @babel/plugin-transform-runtime:

防止打包后的文件中出现重复声明的函数, 它可以重复使用 Babel 注入的 helpers 函数,达到节省代码大小的目的

  • @babel/runtime:

@babel/plugin-transform-runtime这个插件需要@babel/runtime配合使用 供业务代码引入模块化的 Babel helpers 函数

babel的运作流程如下: source code -> @babel/core -> ast -> @babel/traverse 和 @babel/types -> ast -> @babel/generator -> output

但是babel会把esm的import语法变成CommonJS语法,这是不会被浏览器识别的。所以需要用打包工具处理

另外@babel/polyfill这个包文件很大. 一般会用 babel-polyfill 结合 @babel/preset-env + useBuiltins(entry) + preset-env targets 的方案 @babel/preset-env提供插件需要的预设环境, useBuiltins实现polyfill的按需引用 于是就有了下面的.babelrc配置

{
    "presets": [
        [
            "@babel/preset-env",
            {
                "useBuiltIns": "usage",
                "corejs": 3
            }
        ],
        "@babel/preset-react"
    ],
    "plugins": [
        [
            "@babel/plugin-transform-runtime",
        ],
        // 支持 import('xx.js').then(({default: file}) => {})语法
        "dynamic-import-webpack"
    ]
}
复制代码

先关的js处理在webpack配置如下:

const isProdMode = process.argv.indexOf("--mode=production") !== -1
rules: [
    {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: "babel-loader",
        options: {
            cacheDirectory: true, // false是使用缓存, 所以就是对node_modules下的包进行缓存
            cacheCompression: isProdMode // true是对每个Babel的输出将使用Gzip压缩, 开发环境当然不要压缩了
        }
    },
]
复制代码

另外有时候在开发公共包的时候,团队协作可能就需要使用typescript了,typescript该怎么处理配置呢?

  • webpack的配置文件下添加如下处理:
{
    test: /\.ts$/,
    use: "ts-loader",
    exclude: /node_modules/,
},
复制代码
  • 添加ts-loader的配置文件tsconfig.json
{
    "compilerOptions": {
      "outDir": "./dist/",  // 重定向输出目录。
      "allowSyntheticDefaultImports" : true, // 允许从没有设置默认导出的模块中默认导入。
      "module": "es6", // 指定生成哪个模块系统代码
      "target": "es5", // 指定ECMAScript目标版本
      "jsx": "react",
      "sourceMap": true,
      "moduleResolution": "node",
      "allowJs": true //允许ts中引入js
    },
    "include": [
        "mock/**/*",
        "src/**/*",
        "config/**/*",
        ".umirc.ts",
        "typings.d.ts"
      ],
    "exclude": [
        "node_modules",
        "lib",
        "es",
        "dist",
        "typings",
        "**/__test__",
        "test",
        "docs",
        "tests"
    ]
}
复制代码

样式配置

关于css的处理,一般要处理各种浏览器的css样式兼容性,支持scss等。下面是各种loader和插件的解释:

  • require("mini-css-extract-plugin").loder:

把css拆分出来用外链的形式引入css文件,该插件会将所有的css样式合并为一个css文件

  • style-loader:

把css插入到style中

  • css-loader:

@import,url()进行处理

  • sass-loader:

把scss/sass 翻译成css

  • postcss-loader:

使用postcss处理css

  • postcss:

把css解析为一个抽象语法树, 调用插件处理抽象语法树并添加功能

  • autoprefixer:

添加前缀的,解决浏览器兼容问题. 添加webkit, mozilla前缀

  • postcss-plugin-px2rem:

根据跟节点fontsize设置,将px装成rem

在webpack中配置如下:

const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const isProdMode = process.argv.indexOf("--mode=production") !== -1;
const isDevMode = process.argv.indexOf("--mode=development") !== -1;

const getStyleLoaders = (cssOptions, preProcessors) => {
    const loaders = [
        isDevMode && "style-loader",
        isProdMode && MiniCssExtractPlugin.loader,
        {
            loader: require.resolve("css-loader"),
            options: cssOptions,
        },
        {
            loader: require.resolve("postcss-loader"),
            options: {
                postcssOptions: {
                    plugins: [
                        require("postcss-preset-env")({
                            autoprefixer: {
                                overrideBrowderslist: "andoroid >= 4.3",
                            },
                            stage: 3,
                        }),
                        require("postcss-plugin-px2rem")({
                            rootValue: 75,
                            minPixelValue: 2, // 设置要替换的最小像素值
                        }),
                    ],
                },
            },
        },
    ].filter(Boolean); // loader 是在数组的从后往前的顺序先后处理

    if (preProcessors) {
        loaders.push({
            loader: require.resolve(preProcessors),
        });
    }
    return loaders;
}

{
    test: /\.css$/,
    use: getStyleLoaders({
        importLoaders: 1,
    }),
},
{
    test: /\.(scss|sass)$/,
    exclude: /\.module\.(scss|sass)$/,
    // importLoaders 选项告知走该loader之前需要走几个loader
    // 为了防止经过css-loader处理时没有被sass-loader和postcss-loader处理
    use: getStyleLoaders(
        {
            importLoaders: 2,
        },
        "sass-loader"
    ),
},
{
    test: /\.module\.(scss|sass)$/,
    use: getStyleLoaders({
        importLoaders: 2,
        sourceMap: isProdMode,
        modules: {
            localIdentName: '[local]_[hash:base64:6]'
        },
    }, 'sass-loader')
},
复制代码

关于sourceMap

通过sourceMap可以查看报错是出自哪个源文件而不是打包后的文件哪里出错 不过使用sourceMap会使得打包速度变慢 另外默认在module: 'development'环境中sourceMap是默认开启的 下面是各种sourceMap的解释:

  • devtool: 'inline-source-map' 将对应的map文件放到js文件中
  • devtool: 'cheap-inline-source-map' 告诉你具体哪一行出问题,不会精确到那一列出问题
  • devtool: 'eval' eval是打包速度最快的除了你设置devtool:none
  • devtool: 'cheap-module-eval-source-map' 推荐development环境使用
  • devtool: 'cheap-module-source-map' 推荐production环境使用

各种plugin

plugin 可以在webpack运行到某个时刻的时候帮你做一些事情,所以plugin有生命周期这个说法。 下面是一些常用的插件: clean-webpack-plugin: 打包前清除上一次打包的文件

htmlWebpackPlugin: 打包结束后,自动生成一个html文件, 并把打包生成的js自动引入到这个html

MiniCssExtractPlugin: 把css拆分出来用外链的形式引入css文件,该插件会将所有的css样式合并为一个css文件 另外还可以对css进行代码分割

webpack-bundle-analyzer: 打包分析工具

HappyPack: 开启多进程Loader转换,不过还是别用,经常听到电脑风扇的转动声

HotModuleReplacementPlugin: 热更新的模块替换插件

copy-webpack-plugin: 拷贝静态资源

css-minimizer-webpack-plugin: 压缩css

terser-webpack-plugin: 压缩js

tree shaking

这是我写的一个tree shaking demo

去除无用代码, 只支持ESM, 不支持commonJS的require的引入, 因为CommonJS定义的模块化规范,只有在执行代码后,才能动态确定依赖模块,所以不支持tree shaking。毕竟tree shaking的本质是在在编译生成AST以后进行无用代码去除 默认production模式开启tree shaking(由类似terser-webpack-plugin的插件做的处理) 如果想在开发环境中设置tree shaing, 需要这么配置:

// --webpack.config.js
optimization: {
    usedExports: true,
}
// --package.json
'sideEffects': false
// 对所有模块做tree shaking, 不过一般我们不对css等进行tree shaking
// 所以要这么配置
"sideEffects": [
    "*.css",
    "*.scss",
    "*.sass"
],
复制代码

另外有时候我们的代码是作为公共库进行开发,可能在Node环境也要运行,而CommonJs又不支持 tree shaking. 所以一般我们会有下面的处理:

{
  "name": "Library",
  "main": "dist/index.cjs.js", // 打包出一份commonjs规范的bundle
  "module": "dist/index.esm.js", // 打包出一份tree shaking后的ESM规范的bundle
}
复制代码

代码分割(code splitting)

当重新载入文件的时候,因为代码分割到了不同文件, 只需要对变更代码的文件重新加载即可,所以提升了加载性能 在webpack中配置如下:

optimization: {
    // 把runtime部分的代码抽离出来单独打包
    // runtime部分的代码是管理各个模块的连接(模块之间的引用关系)
    runtimeChunk: {
        name: "runtime",
    },
    splitChunks: {
        // 对同步代码和异步代码都进行代码分割
        // 如果是async那就是只对异步代码做代码分割
        chunks: "all",
        minSize: 30000, // 表示抽取出来的文件在压缩前的最小大小,默认为30000
        maxSize: 0, // 表示抽取出来的文件在压缩前的最大大小,默认为 0,表示不限制最大大小;
        minChunks: 1, // 引入的模块最少引入次数,超过才打包
        maxAsyncRequests: 6, // 同时加载的模块树
        maxInitialRequests: 4,
        automaticNameDelimiter: "~", // 组和文件名的连接符
        cacheGroups: {
            vendors: {
                name: 'vendors', // 对这种打包文件放到vendors.js中
                enforce: true, // 忽略默认参数
                test: /[\\/]node_modules[\\/]/, // 打包的文件来自node_modules
                priority: 20, // 不同组的优先级
                reuseExistingChunk: true,
            },
            // MiniCssExtractPlugin会对生成的css进行代码分割
            // 然后打包到styles.css的文件中
            styles: {
                name: 'styles',
                test: /\.(scss|sass|css|less)$/,
                chunks: 'all',
                enforce: true,
                priority: 10,
            },
            // 默认组
            default: {
                minChunks: 2,
                priority: -20, // 同时满足多个组条件,谁的优先级高就用谁
                reuseExistingChunk: true, // 如果一个模块已经被打包了,就用之前打包的模块做引用
            },
        },
    },
},
复制代码

最后

另外还需要对webpack进行

  1. 多环境配置
  2. 支持多页面入口打包配置
  3. 代码打包分析

这是我写的一个webpack配置

文章分类
前端
文章标签