Webpack

webpack

用于现代 JavaScript 应用程序的静态模块打包工具;

基本配置

请通读文档并动手尝试,webpackjs.com更新较为滞后,请阅读官方文档

包括但不限于以下常见的配置项:

  1. mode:webpack 的工作模式,默认 production,可选 development 及 none;

  2. entry:入口配置,如:

    1
    2
    3
    4
    entry: {
    // 入口名对应的入口文件
    index: "./src/index.js";
    }
  3. output:输出配置,如:

    1
    2
    3
    4
    5
    6
    7
    8
    output: {
    // 输出文件,[name] 动态获取 entry 的入口名
    filename: "[name].bundle.js",
    // 输出目录,绝对路径
    path: path.resolve(__dirname, 'dist'),
    // 每次构建清空输出目录
    clean: true
    }
  4. module:决定如何处理项目中不同类型的模块(文件),如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    module: {
    rules: [
    {
    // 处理 css
    test: /\.css$/i,
    use: ["style-loader", "css-loader"],
    },
    {
    // 处理图片
    test: /\.(png|svg|jpg|gif)$/i,
    use: [
    {
    loader: "file-loader",
    options: {},
    },
    {
    loader: "url-loader",
    options: {
    limit: 5 * 1024,
    },
    },
    ],
    },
    ],
    }
  5. plugins:通过内置插件或第三方插件自定义 webpack 构建过程,如:

    1
    2
    3
    4
    5
    6
    plugins: [
    // 生成 index.html 并自动引入 webpack 生成的 bundle
    new HtmlWebpackPlugin(),
    // 热更新
    new webpack.HotModuleReplacementPlugin(),
    ],
  6. devtool:控制是否生成 source map,如:

    1
    devtool: "inline-source-map",
  7. devServer:自动重载修改后的代码,需要安装 webpack-dev-server 插件,如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    devServer: {
    // 静态文件路径
    static: './dist',
    // 监听端口
    port: 9008,
    // 自动打开浏览器
    open: true,
    // 跨域代理
    proxy: {
    // 将 /api 的请求代理到 http://localhost:8888/api
    '/api': "http://localhost:8888",
    // 将 /api 的请求代理到 http://localhost:8888
    '/api': {
    target: 'http://localhost:8888',
    pathRewrite: {'^api', ''}
    }
    }
    }
  8. 使用 webpack-merge 拆分配置文件;

    webpack 5 引入方式变化const { merge } = require("webpack-merge");

高级配置(webpack 常见的性能优化)

优化打包效率

  1. 优化 babel-loader

    • 使用缓存;
    • 限制打包范围;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    module: {
    rules: [
    {
    test: /\.js$/i,
    include: path.resolve(__dirname, "src"),
    loader: "babel-loader?cacheDirectory",
    },
    ],
    }
  2. IgnorePlugin:第三方模块按需导入,避免引入无用模块,减小代码体积,提高构建速度;

    1
    2
    3
    4
    5
    6
    plugins: [
    new webpack.IgnorePlugin({
    // 直接引入 moment/local/ 将被忽略
    resourceRegExp: /moment\/locale\//,
    })
    ],
  3. module.noParse:避免重复的打包,如:

    1
    2
    3
    module: {
    noParse: /\.min\.js$/,
    }
  4. HappyPack:启用多进程打包,提高构建速度,如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    module: {
    rules: [
    {
    test: /\.js$/i,
    use: 'HappyPack/loader?id=js'
    },
    ],
    },
    plugins: [
    new HappyPack({
    // HappyPack 的全局唯一 id,用来匹配 rules
    id: 'js',
    loaders: ['babel-loader'],
    })
    ],
  5. ParallelUglifyPlugin:启用多进程压缩 JS,如:

    1
    2
    3
    4
    plugins: [
    // 启用 ParallelUglifyPlugin
    new ParallelUglifyPlugin()
    ],
  6. DllPlugin:将不常改动的源文件拆分成单独的 bundle,如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    entry: {
    jQuery: ['jQuery']
    },
    output: {
    filename: '[name].dll.js',
    path: path.resolve(__dirname, 'dist'),
    // 动态链接库的全局变量前缀,防止命名冲突
    library: '_dll_[name]',
    },
    plugins: [
    new DllPlugin({
    // 动态链接库变量名,和 output.library 保持一致
    name: '_dll_[name]',
    path: path.resolve(__dirname, 'dist', '[name].manifest.json')
    })
    ],

优化产出代码

  1. 小图片 base64 编码,如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    module: {
    rules: [
    {
    test: /\.(png|svg|jpg|gif)$/i,
    use: [
    {
    loader: "file-loader",
    options: {},
    },
    {
    loader: "url-loader",
    options: {
    limit: 5 * 1024,
    outputPath: '/images/'
    },
    },
    ],
    },
    ],
    }
  2. bundle 使用 hash,内容不变则使用缓存,如:

    1
    2
    3
    4
    output: {
    // contentHash 变为小写 contenthash
    filename: "bundle.[contenthash:8].js",
    },
  3. 懒加载,使用 ES6 Module 语法延迟加载模块;

    1
    2
    3
    4
    5
    6
    7
    setTimeout(() => {
    import("./module.js").then((module) => {
    // 必须使用 default
    const print = module.default;
    print();
    });
    }, 2000);
  4. MiniCssExtractPlugin:从 JS 中抽离 CSS 到单独的文件,如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    module: {
    rules: [
    { // 抽离 css
    test: /\.css$/,
    loader: [
    // 注意,这里不再使用 style-loader
    MiniCssExtractPlugin.loader,
    'css-loader',
    ]
    },
    ]
    },
    plugins: [
    // 抽离 css 文件
    new MiniCssExtractPlugin({
    filename: 'css/main.[contentHash:8].css'
    })
    ],
    optimization: {
    minimizer: [
    // 压缩 css
    new CssMinimizerWebpackPlugin()
    ],
    }

    MiniCssExtractPlugin webpack5 已内置,不需要 🙅‍♂️ 安装插件了;

  5. splitChunks:提取公共代码,避免重复打包,减小代码体积,如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    optimization: {
    splitChunks: {
    // all 表示同步,异步代码都分割
    chunks: 'all',
    cacheGroups: {
    // 第三方模块
    vendor: {
    // 只要是 node_modules 中的模块就包含在 vendor 中
    test: /node_modules/
    // chunk 名
    name: 'vendor',
    // 小于设定大小则不分割
    miniSize: 0,
    // 最少被复用的次数
    miniChunks: 1,
    // 抽离优先级
    priority: 1,
    },
    // 公共模块
    common: {
    // chunk 名
    name: 'common',
    // 小于设定大小则不分割
    miniSize: 3 * 1024,
    // 最少被复用的次数
    miniChunks: 2,
    // 抽离优先级
    priority: 0,
    }
    }
    },

    }
  6. IgnorePlugin:第三方模块按需加载,减小代码体积;

  7. 使用 CDN 来加载静态资源,提高访问速度;

  8. 使用mode: 'production' 选项,webpack 会进行以下操作:

    • 自动开启代码压缩;

    • 某些框架依赖于mode: 'production'自动删除调试代码;

    • 自动启用 Tree-Shaking,删除未使用的代码;

      必须使用 ES6 Module 才能让 Tree-Shaking 生效;

      ES6 Module 是静态引入,编译时引用;

      commonjs 是动态引入,执行时引用;

      只有 ES6 Module 才能静态分析,实现 Tree-Shaking;

module、chunk、bundle 分别是什么,有何区别?

一份源码在不同场景下的名字;

  1. module 是各个源码文件,包括第三方模块和自己编写的源码,,webpack 中一切皆模块;
  2. chunk 是多个模块的合成,webpack 处理时在内存中生成的就是 chunk;
  3. bundle 是最终的输出文件,一般一个 chunk 对应一个 bundle;

loader 和 plugin 的区别

  1. loader 是模块转换器,让 webpack 能够处理其他类型的文件,并将其转换为有效的模块;

  2. plugin 是扩展增强,让 webpack 可以执行范围更广的任务,如 打包优化,资源管理,注入环境变量等;

webpack 构建流程概述

  1. 初始化:启动构建,从配置文件中读取合并参数,加载 plugin,实例化 complier 对象;
  2. 编译,从 entry 出发,用配置中的每个 loader 对模块进行编译和翻译,并找出依赖关系,递归进行编译处理;
  3. 输出:将编译后的模块根据依赖关系组合成一个个包含多个模块的 chunk,再把每个 chunk 根据配置中的路径和文件名转换成 bundle 并输出到文件系统中;

babel

JavaScript 编译器,将 ES2015+ 的语法编译为向后兼容的 JS 语法,使 JS 应用程序可以运行在当前和旧版本的环境中;

preset 和 plugin

preset 是常用 plugin 的集合,在 preset 无法满足要求时,可以在 plugin 中进一步扩展;

babel-polyfill

babel-polyfill 是 corejs 和 regenerator 库的集合,包含了常用的 polyfill;

babel-runtime 和 babel-polyfill

babel-runtime 变量会重新取名,不污染全局环境;
babel-polyfill 会污染全局环境,可能会造成命名冲突或变量覆盖;

webpack 和 babel 的区别

  1. webpack 是打包构建工具,不关心语法,是多个 loader 和 plugin 的集合;
  2. babel 是新型语法编译工具,不关心模块化;

前端为何要进行打包和构建

随着互联网的发展,前端项目越来越复杂,促使前端开发向模块化发展,模块化可以帮助我们更好的解决复杂应用开发过程中的代码组织问题,也提高了代码复用性,使得开发更加快捷和高效;

但随着模块化思想的引入,在开发过程中又产生了许多新的问题,如:客户端环境不统一导致的兼容性问题,模块化导致项目资源零散,网络请求增多带来的服务器压力增大、用户体验差等问题促使前端开发中 webpack 等一系列打包构建工具的出现,让我们的应用开发既能享受模块化带来的便利,又不必担心模块化对生产环境产生影响;

在代码层面

  1. 代码经过合并压缩,体积更小,加载速度更快;
  2. 开发中可以使用 TS、ES6、scss、less 等高级语言或语法,集成了 polyfill、postcss 等提高兼容性的插件,提高开发效率的同时也不必担心兼容性问题;
  3. 同时集成了语法检查等工具,在开发过程中就可以及时发现错误并改正,降低了代码出错的几率,节省了 debug 时间;

在开发流程方面

  1. 统一高效的开发环境,以及统一的构建流程和产出标准,大大的提高了项目开发的质量;
  2. 集成公司构建规范,使得项目的开发可以标准化、高质量、高效率的完成;