乐趣区

webpack从零开始

一. 基础配置

1.init 项目

mkdir react-webpack-demo
cd react-webpack-demo
mkdir src
mkdir dist
npm init -y

2. 安装 webpack

安装 webpack, 并新建 webpack.js 文件,并初始化文件

yarn add webpack  webpack-cli webpack-dev-server -D
mkdir config
touch config/webpack.common.js
module.exports = {entry: ['./src/index.js'],// 入口
    output: { // 出口
        path: paths.appBuild
    },
    module: {}, // 配置 loader
    plugins: [], // 插件};

3. 安装 react react-dom

yarn add react react-dom

4. 配置 loader

  1. loaders
    loader 用来解析文件转译成浏览器可以识别的文件。如.less、.jsx 等这些文件浏览器是不能正常转译的,loaders 的作用就是充当着 ’ 翻译 ’ 的作用。
  2. babel 输入源码 => 输出编译后的代码,总共分为三个阶段:解析,转换,生成。
    babel 本身不具有任何转化功能,它把转化的功能都分解到一个个 plugin 里面。因此当我们不配置任何插件时,经过 babel 的代码和输入是相同的。
    插件总共分为两种: 语法插件(Babel 解析(parse)特定类型的语法,转换插件会自动启用语法插件), 转换插件(例如:箭头函数 (a) => a 就会转化为 function (a) {return a})
  3. 预设(Presets)
    preset 可以作为 Babel 插件的组合;
    Preset 是逆序排列的(从后往前)
    @babel/preset-react 就是一个官方 Preset
  4. @babel/polyfill
    1.babel 默认只转换 js 语法,而不转换新的 API,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法 (比如 Object.assign) 都不会转码。
    2.babel-polyfill 会污染全局变量,给很多类的原型链上都作了修改,如果我们开发的也是一个类库供 其他开发者使用,这种情况就会变得非常不可控。
    3. 类库开发,通常我们会倾向于使用 babel-plugin-transform-runtime
    4.@babel/preset-env 获取您指定的任何目标环境,, 按需转码, 引入相应的插件,默认使用 browserslist

    @babel/core-babel  核心模块    
    @babel/preset-env  编译 ES6 等 
    @babel/preset-react  转换 JSX
    @babel/preset-react  转换 JSX
    @babel/plugin-transform-runtime 避免 polyfill 污染全局变量,减小打包体积
    @babel/polyfill  ES6 内置方法和函数转化垫片
    
    
    
       

配置

     {test: /\.(js|jsx)$/,
    include: paths.appSrc,
    use: [
        {
            loader: 'babel-loader',
            options: {presets: ['@babel/preset-react'],
                plugins: [
                    // 按需加载 lodash
                    'lodash',
                    // babel-plugin-import
                    // true 是 less,可以写 'css' 如果不用 less
                    ['import', { libraryName: 'antd', libraryDirectory: 'es', style: 'less'}],
                    [
                        '@babel/plugin-transform-runtime',
                        {
                            absoluteRuntime: false,
                            corejs: false,
                            helpers: false,
                            regenerator: true, // generator 不会污染全局的
                            useESModules: false, // 转换将使用无法运行的帮助程序
                        },
                    ],
                    // '@babel/plugin-syntax-dynamic-import'
                ],
                cacheDirectory: true,
                cacheCompression: isEnvProduction,
                compact: isEnvProduction,
            },
        },
    ],
}

6. 按需引入 Polyfill

Polyfill 是一个 js 库,主要抚平不同浏览器之间对 js 实现的差异。根据浏览器不同的 UA 按需加载 polyfill, 国内浏览器支持不好。
<script crossorigin=”anonymous” src=”https://polyfill.io/v3/polyfi…;></script>

7. CSS loader

  1. style-loader : 以 <style></style> 形式在 html 页面中插入 css 代码
  2. css-loader : 加载.css 文件 ,options{Minimize: 是否开启 css 代码压缩。modules:是否开启 css-modules}
  3. postcss-loader : 添加浏览器前缀
  4. less-loader less 转 css

可以通过匹配文件是否带 module 分别配置是否要开始 modules

const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;

module 配置

   modules: {
        mode: 'local',
        localIdentName: '[local]--[hash:base64:5]',
        context: resolve(__dirname, 'src'),
        hashPrefix: 'my-less-hash',
    },

8. 使用 source-map, 对 devtool 进行优化

  1. devtool:”cheap-module-eval-source-map”,// 开发环境配置
  2. devtool:”cheap-module-source-map”, // 线上生成配置

9. 使用 WebpackDevServer

  devServer: {
    hot: true,
    contentBase: paths.appBuild,
    host: "0.0.0.0", // 可以使用手机访问
    port: 8080,
    historyApiFallback: true, // 该选项的作用所有的 404 都连接到 index.html
    compress: true,
    inline: true,
  }

二 优化

1. 文件路径优化

  1. extension: 指定 extension 之后, 引入模块的时候,可以不用扩展名
  2. alias: 配置别名可以加快 webpack 查找模块的速度
  3. modules: 去哪些目录下寻找第三方模块
   resolve: {
        // 引入模块的时候,可以不用扩展名
        extensions: ['.js', '.json', '.jsx'],
        // 别名
        alias: {'@assets': resolve(__dirname, '../src/assets'),
            '@components': resolve(__dirname, '../src/components'),
            '@pages': resolve(__dirname, '../src/pages'),
        },
        // 添加一个目录到模块搜索目录
        modules: [resolve(__dirname, '../src'), 'node_modules'],
    },

2. 静态资源托管到 CDN 时

  1. filename:决定了每个输出 bundle 的名称
  2. path:这些 bundle 将写入到 output.path 选项指定的目录下。
  3. publicPath:将上面两个准备的路径字符串前面加上外部路径,比如某个 CDN:http://xx.xx.com/public/,此时原资源位置:js/xx.xxxxx.bundle.js –> http://xx.xx.com/public/js/xx…

3.MiniCssExtractPlugin,抽取 css 文件

css 是直接打包进 js 里面的. 单独生成 css,css 可以和 js 并行下载,提高页面加载效率,
loader 和 plugins 都要配置

4. 缓存

  1. hash(所有文件哈希值相同,只要改变内容跟之前的不一致,所有哈希值都改变,没有做到缓存意义)
  2. chunkhash(同一个模块,就算将 js 和 css 分离,其哈希值也是相同的,修改一处,js 和 css 哈希值都会变,同 hash,没有做到缓存意义)
  3. contenthash(只要文件内容不一样,产生的哈希值就不一样)

output 和 new MiniCssExtractPlugin() 时, 配置 filename: ‘static/css/[name].[contenthash:8].css’,

5. 代码分割按需加载、提取公共代码

optimization

 optimization: {
        minimize: isEnvProduction,
        // 找出模块的顺序,最小的初始包,product 默认开启
        occurrenceOrder: true,
        // runtimeChunk: 会为每个仅含有 runtime 的入口起点添加一个额外 chunk;
        // 值 "single" 会创建一个在所有生成 chunk 之间共享的运行时文件。此设置是如下设置的别名:runtimeChunk: {name: 'manifest'},
        // usedExports 不导出未使用的代码
        usedExports: true,
        concatenateModules: true,
        splitChunks: {// chunks: 'async', // 必须三选一:"initial" | "all"(推荐) | "async" (默认就是 async)
            minSize: 30000, // 最小尺寸,默认值是 30kb
            minChunks: 1, // 最小 chunk,默认 1
            maxAsyncRequests: 5, // 最大异步请求数,默认 5
            maxInitialRequests: 3, // 最大初始化请求数,默认 3
            automaticNameDelimiter: '-', // 打包分隔符
            name: true, // 打包后的名称,此选项可接收 function
            // 设置缓存组, 用来抽取满足不同规则的 chunk
            cacheGroups: {
                // 这里开始设置缓存的 chunks
                vendor: {
                    //  key 为 entry 中定义的 入口名称
                    test: /[\\/]node_modules[\\/]/, // 正则规则验证,如果符合就提取 chunk
                    chunks: 'initial',
                    name: 'vendor', // 要缓存的 分隔出来的 chunk 名称
                    priority: 10,
                    enforce: true,
                },
                lazy: {test: ({ resource}) => {return /antd/.test(resource);
                    },
                    chunks: 'async',
                    name: 'lazy',
                    priority: 10,
                    enforce: true,
                },
                commons: {
                    chunks: 'all',
                    test: ({resource}) => {return /[\\/]node_modules[\\/]/.test(resource) && !shouldExcludeFromCommon.test(resource);
                    },
                    name: 'common',
                    minChunks: 2,
                    maxInitialRequests: 5,
                    minSize: 0,
                    priority: 20,
                },
            },
        },
    },

6. 文件压缩

webpack4 只要在生产模式下,代码就会自动压缩

7. 暴露全局变量

 new webpack.ProvidePlugin({fetch: 'imports-loader?this=>global!exports-loader?global.fetch!whatwg-fetch',}),

8. 指定环境, 定义环境变量

DefinePlugin 允许创建一个在编译时可以配置的全局常量, 这些值会被内联进那些允许传一个代码压缩参数的代码中,从而减少冗余的条件判断

new webpack.DefinePlugin({PRODUCTION: JSON.stringify(true),
  VERSION: JSON.stringify("5fa3b9"),
  BROWSER_SUPPORTS_HTML5: true,
  TWO: "1+1",
  "typeof window": JSON.stringify("object")
})

9. css tree sharking

npm i glob-all purify-css purifycss-webpack --save-dev
 
const PurifyCSS = require('purifycss-webpack')
const glob = require('glob-all')
plugins:[
    // 清除无用 css
    new PurifyCSS({
      paths: glob.sync([
        // 要做 CSS Tree Shaking 的路径文件
        path.resolve(__dirname, './src/*.html'), // 请注意,我们同样需要对 html 文件进行 tree shaking
        path.resolve(__dirname, './src/*.js')
      ])
    })
]

10. js Tree Shaking

清除到代码中无用的 js 代码,只支持 import 方式引入,不支持 commonjs 的方式引入
只要 mode 是 production 就会生效,develpoment 的 tree shaking 是不生效的,因为 webpack 为了方便你的调试

optimization: {

usedExports:true,

}

10. 注意点

  1. rule 配置顺序影响构建,无用的 rule 全部去掉。
  2. loader 加载顺序: 从右向左:Webpack 选择了 compose 方式

11. 页面缓存

缓存一次后 服务器挂了 还可以用
// // 预缓存
new GenerateSW({

clientsClaim: true,
skipWaiting: true,
importWorkboxFrom: 'local',
include: [/\.js$/, /\.css$/, /\.html$/, /\.jpg/, /\.jpeg/, /\.svg/, /\.webp/, /\.png/],

}),

12. webpack-merge

提取公共配置
module.exports = merge(common, webpackConfig);

13. 使用 HappyPack

MacBook Pro 2017 上测试了几把,并没有提升速度,反而慢了,后续再看。

三 常见问题

1. antd 打包后 icons dist 文件很大

resolve: {

    alias: {'@ant-design/icons/lib/dist$': resolve(__dirname, '../src/assets/icons.js')
    },

}
并在 icons.js 配置

2. antd 按需引入 配置 less 是报错

  1. 按需引入 antd 配置: 在 babel-loader plugins 中添加 如下配置
  2. 需要先安装 babel-plugin-import
  3. 导入 js 和 css 模块(LESS/Sass 源文件): style: true
  4. 导入 js 和 css 模块(css 内置文件) style:”css”
    [‘import’, { libraryName: ‘antd’, libraryDirectory: ‘es’, style: true}],

如果是 less 会报错: 需要在 less-loader option 里面添加 javascriptEnabled: true,
如果添加自定义主题: 添加 modifyVars:
{

'primary-color': '#1DA57A',
'link-color': '#1DA57A',
'border-radius-base': '2px',

},

3. antd 需要自行引入 moment

在 index.html 文件引入

4. css module

 module: {
    rules: [
      {
        test: /\.css$/i,
        loader: 'css-loader',
        options: {
          modules: {
            mode: 'local',
            localIdentName: '[path][name]__[local]--[hash:base64:5]',
            context: path.resolve(__dirname, 'src'),
            hashPrefix: 'my-custom-hash',
          },
        },
      },
    ],
  },

5.UglifyjsWebpackPlugin 不支持 ES6 语法

使用 terser-webpack-plugin

退出移动版