跳至主要內容

Webpack 核心概念

Mr.LRH大约 9 分钟

Webpack 核心概念

webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

  • module :不同文件类型的模块。Webpack 就是用来对模块进行打包的工具,这些模块各种各样,比如:js 模块、css 模块、sass 模块、vue 模块等等不同文件类型的模块。这些文件都会被 loader 转换为有效的模块,然后被应用所使用并且加入到依赖关系图中。
  • chunk :数据块。
    • 非初始化的数据块。例如在打包时,对于一些动态导入的异步代码,Webpack 分割出共用的代码,可以是写的代码模块,也可以是第三方库,这些被分割的代码文件就可以理解为 chunk
    • 初始化的的数据块。入口文件处 (entry point) 的各种文件或者说模块依赖,就是 chunk ,它们最终会被捆在一起打包成一个输出文件,输出文件可以理解为 bundle,当然它其实也是 chunk
  • bundle :包含一个或多个 chunk,是源码经过 webpack 处理后的最终版本

context

context 即项目打包的相对路径上下文,必须是一个绝对路径,用于从配置中解析入口 起点(entry point)loader。默认使用当前目录。

entry(入口)

入口起点(entry point) 指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。默认值为 ./src

module.exports = {
  // ========== 单个入口 ==========
  entry: {
    index: 'index.js',
  },
  // ========== 多个入口 ==========
  // entry: {
  //   index: ['index.js', 'app.js'],
  //   vendor: 'vendor.js',
  // },
};

output(输出)

output 属性 告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist

const path = require('path');
module.exports = {
  // ========== 单个出口 ==========
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'), // 输出路径
    filename: 'index.min.js', // 输出文件名
  },

  // ========== 多个出口 ==========
  // entry: {
  //   index: 'index.js',
  //   vendor: 'vendor.js',
  // },
  // output: {
  //   path: path.resolve(__dirname, 'dist'),
  //   filename: '[name].min.[hash:5]js',
  // },
};

常用配置:

  • output.chunkFilename :决定了非入口(non-entry) chunk 文件的名称。

  • output.filename :决定了每个输出 bundle 的名称。这些 bundle 将写入到 output.path 选项指定的目录下。该项不会影响 按需加载 chunk 的输出文件。对于这些文件,使用 output.chunkFilename 选项来控制输出。通过 loader 创建的文件也不受影响。可以使用以下占位符:

    • [hash] :模块标识符(module identifier)的 hash。可以使用 [hash:16](默认为20)来指定,也可通过指定 output.hashDigestLength 在全局配置长度
    • [chunkhash] :chunk 内容的 hash。hash 长度同 [hash] 上配置
    • [name] :模块名称
    • [id] :模块标识符(module identifier)
    • [query] :模块的 query,例如,文件名 ? 后面的字符串
  • output.path :输出路径。

  • output.publicPath :指定在浏览器中所引用的 此输出目录对应的公开 URL

  • output.library :指定库的名称,库的名称支持占位符和普通字符串。

  • output.libraryTarge :取值范围为:varassignthiswindowglobalcommonjscommonjs2commonjs-moduleamdumdumd2jsonp,默认是 var

    // 【 libraryTarge: var 】
    // 默认值。当 library 加载完成,入口起点的返回值将分配给一个变量
    {
      output: {
        library: 'myLib',
        filename: 'var.js',
        libraryTarget: 'var'
      }
    }
    // output
    var myLib = (function(modules) {})({
      './src/index.js': function(module, exports) {}
    });
    
    // ===============================================
    
    // 【 libraryTarge: assign 】
    // 将产生一个隐含的全局变量,可能会潜在地重新分配到全局中已存在的值(谨慎使用)
    {
      output: {
        library: 'myLib',
        filename: 'assign.js',
        libraryTarget: 'assign'
      }
    }
    // output: 少了个 var
    myLib = (function(modules) {})({
      './src/index.js': function(module, exports) {}
    });
    
    // ===============================================
    
    // 【 libraryTarge: this 】
    // 入口起点的返回值将分配给 this 的一个属性(此名称由 output.library 定义)下
    {
      output: {
        library: 'myLib',
        filename: 'this.js',
        libraryTarget: 'this'
      }
    }
    // output
    this["myLib"] = (function(modules) {})({
      './src/index.js': function(module, exports) {}
    });
    
    // ===============================================
    
    // 【 libraryTarge: window 】
    // 入口起点的返回值将使用 output.library 中定义的值,分配给 window 对象的这个属性下
    {
      output: {
        library: 'myLib',
        filename: 'window.js',
        libraryTarget: 'window'
      }
    }
    // output
    window["myLib"] = (function(modules) {})({
      './src/index.js': function(module, exports) {}
    });
    
    // ===============================================
    
    // 【 libraryTarge: global 】
    // 入口起点的返回值将使用 output.library 中定义的值,分配给 global 对象的这个属性下
    {
      output: {
        library: 'myLib',
        filename: 'global.js',
        libraryTarget: 'global'
      }
    }
    // output:注意 target=node 的时候才是 global,默认 target=web下 global 为 window
    window["myLib"] = (function(modules) {})({
      './src/index.js': function(module, exports) {}
    });
    
    // ===============================================
    
    // 【 libraryTarge: commonjs 】
    // 入口起点的返回值将使用 output.library 中定义的值,分配给 exports 对象
    {
      output: {
        library: 'myLib',
        filename: 'commonjs.js',
        libraryTarget: 'commonjs'
      }
    }
    // output
    exports["myLib"] = (function(modules) {})({
      './src/index.js': function(module, exports) {}
    });
    
    // ===============================================
    
    // 【 libraryTarge: amd 】
    // 将你的 library 暴露为 AMD 模块
    {
      output: {
        library: 'myLib',
        filename: 'amd.js',
        libraryTarget: 'amd'
      }
    }
    // output
    define('myLib', [], function() {
      return (function(modules) {})({
          './src/index.js': function(module, exports) {}
      });
    });
    
    // ===============================================
    
    // 【 libraryTarge: umd 】
    // 将 library 暴露为所有的模块定义下都可运行的方式。它将在 CommonJS, AMD 环境下运行,或将模块导出到 global 下的变量
    {
      output: {
        library: 'myLib',
        filename: 'umd.js',
        libraryTarget: 'umd'
      }
    }
    // output
    (function webpackUniversalModuleDefinition(root, factory) {
      if (typeof exports === 'object' && typeof module === 'object') module.exports = factory();
      else if (typeof define === 'function' && define.amd) define([], factory);
      else if (typeof exports === 'object') exports['myLib'] = factory();
      else root['myLib'] = factory();
    })(window, function() {
      return (function(modules) {})({
          './src/index.js': function(module, exports) {}
      });
    });
    
    // ===============================================
    
    // 【 libraryTarge: commonjs2 】
    // 入口起点的返回值将分配给 module.exports 对象。这个名称也意味着模块用于 CommonJS 环境
    {
      output: {
        library: 'myLib',
        filename: 'commonjs2.js',
        libraryTarget: 'commonjs2'
      }
    }
    // output
    module.exports = (function(modules) {})({
      './src/index.js': function(module, exports) {}
    });
    
    // ===============================================
    
    // 【 libraryTarge: umd2 】
    {
      output: {
        library: 'myLib',
        filename: 'umd2.js',
        libraryTarget: 'umd2'
      }
    }
    // output
    (function webpackUniversalModuleDefinition(root, factory) {
      if (typeof exports === 'object' && typeof module === 'object') module.exports = factory();
      else if (typeof define === 'function' && define.amd) define([], factory);
      else if (typeof exports === 'object') exports['myLib'] = factory();
      else root['myLib'] = factory();
    })(window, function() {
      return (function(modules) {})({
          './src/index.js': function(module, exports) {
          }
      });
    });
    
    // ===============================================
    
    // 【 libraryTarge: commonjs-module 】
    {
      output: {
        library: 'myLib',
        filename: 'commonjs-module.js',
        libraryTarget: 'commonjs-module'
      }
    }
    // output
    module.exports = (function(modules) {})({
      './src/index.js': function(module, exports) {}
    });
    
    // ===============================================
    
    // 【 libraryTarge: jsonp 】
    // 将把入口起点的返回值,包裹到一个 jsonp 包装容器中
    {
      output: {
          library: 'myLib',
          filename: 'jsonp.js',
          libraryTarget: 'jsonp'
      }
    }
    // output
    myLib((function(modules) {})({
      './src/index.js': function(module, exports) {}
    }));
    

resolve(解析)

resolve 帮助 Webpack 查找依赖模块的,通过 resolve 的配置,可以帮助 Webpack 快速查找依赖,也可以替换对应的依赖(比如开发环境用 dev 版本的 lib 等)。

常用配置:

  • resolve.extensions :帮助 Webpack 解析扩展名的配置,默认值:['.wasm', '.mjs', '.js', '.json']

  • resolve.alias :设置别名。通过设置 alias 可以帮助 webpack 更快查找模块依赖。

    module.exports = {
      resolve: {
        alias: {
          src: path.resolve(__dirname, 'src'),
          '@lib': path.resolve(__dirname, 'src/lib'),
          // 在名称末尾添加 $ 符号来缩小范围,只命中以关键字结尾的导入语句,可以做精准匹配
          // eg: import react from 'react' // 精确匹配,所以 react.min.js 被解析和导入
          // eg: import file from 'react/file.js'; // 非精确匹配,触发普通解析
          react$: '/path/to/react.min.js'
        },
      },
    };
    
  • resolve.mainFields :当从 npm 包中导入模块时(例如,import * as D3 from "d3"),此选项将决定在 package.json 中使用哪个字段导入模块。根据 webpack 配置中指定的 target (构建目标)不同,默认值也会有所不同。当 target 属性设置为 webworker, web 或者没有指定,默认值为:mainFields: ["browser", "module", "main"]

    // D3 的 package.json 含有这些字段:
    {
      ...
      main: 'build/d3.Node.js',
      browser: 'build/d3.js',
      module: 'index',
      ...
    }
    

module(模块)

在 webpack 解析模块的同时,不同的模块需要使用不同类型的模块处理器来处理,这部分的设置就在module配置中。module 有两个配置:module.noParsemodule.rules

  • module.noParse :可以让 Webpack 忽略对部分没采用模块化的文件的递归解析和处理,可以提高构建性能

    module.exports = {
      module: {
        // 使用正则表达式
        noParse: /jquery|lodash/
    
        // 使用函数,从 Webpack 3.0.0 开始支持
        noParse: (content) => {
          // content 代表一个模块的文件路径
          // 返回 true or false
          return /jquery|lodash/.test(content);
        },
      },
    };
    
  • module.rules :在处理模块时,将符合规则条件的模块,提交给对应的处理器来处理,通常用来配置 loader,其类型是一个数组,数组里每一项都描述了如何去处理部分文件。每一项 rule 大致可以由以下三部分组成:

    • 条件匹配:通过 testincludeexclude 等配置来命中可以应用规则的模块文件;
    • 应用规则:对匹配条件通过后的模块,使用 use 配置项来应用 loader,可以应用一个 loader 或者按照从后往前的顺序应用一组 loader,还可以分别给对应 loader 传入不同参数;
    • 重置顺序:一组 loader 的执行顺序默认是从后到前(或者从右到左)执行,通过 enforce 选项可以让其中一个 loader 的执行顺序放到最前(pre)或者是最后(post)

loaders

loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。

module.exports = {
  module: {
    rules: [
      // test 属性:用于标识出应该被对应的 loader 进行转换的某个或某些文件。
      // use 属性:表示进行转换时,应该使用哪个 loader。
      { test: /\.css$/, use: 'css-loader' },
    ],
  },
};

常用 loaders :

  • 编译相关:babel-loaderts-loader
  • 样式相关:style-loadercss-loaderless-loaderpostcss-loader
  • 文件相关:file-loaderurl-loader

plugins(插件)

plugins(插件) 目的在于解决 loader 无法实现的其他事。参与打包整个过程、打包优化和压缩、配置编译时的变量、极其灵活。

const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装
const webpack = require('webpack'); // 用于访问内置插件

const config = {
  module: {
    rules: [{ test: /\.txt$/, use: 'raw-loader' }],
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin(),
    new HtmlWebpackPlugin({ template: './src/index.html' }),
  ],
};

module.exports = config;

常用 plugins :

  • 优化相关:CommonsChunkPluginUglifyjsWebpackPlugin
  • 功能相关:ExtractTextWebpackPluginHtmlWebpackPluginHotModuleReplacementPluginCopyWebpackPlugin

模式

通过选择 developmentproduction 之中的一个,来设置 mode 参数,可以启用相应模式下的 webpack 内置的优化。

target(构建的目标)

通过设置 target 来指定构建的目标(target),值有两种类型:stringfunction

  • string 类型值

    • web :默认,编译为类浏览器环境里可用
    • node :编译为类 Node.js 环境可用(使用 Node.js require 加载 chunk)
    • async-node :编译为类 Node.js 环境可用(使用 fs 和 vm 异步加载分块)
    • electron-main :编译为 Electron 主进程
    • electron-renderer :编译为 Electron 渲染进程
    • node-webkit :编译为 Webkit 可用,并且使用 jsonp 去加载分块。支持 Node.js 内置模块和 nw.gui 导入(实验性质)
    • webworker :编译成一个 WebWorker
  • function 类型:接收一个 compiler 作为参数

    const webpack = require('webpack');
    
    const options = {
      target: (compiler) => {
        compiler.apply(
          new webpack.JsonpTemplatePlugin(options.output),
          new webpack.LoaderTargetPlugin('web')
        );
      },
    };
    

externals

externals 提供了 从输出的 bundle 中排除依赖 的方法,减小打包文件的体积。

通常在开发自定义 js 库(library)的时候用到,用于去除自定义 js 库依赖的其他第三方 js 模块。

devtool

devtool 控制是否生成,以及如何生成 source map。使用 SourceMapDevToolPlugin 进行更细粒度的配置。常用配置值:

  • source-map :原始源代码
  • cheap-module-eval-source-map :原始源代码(仅限行)

生产环境不使用或者使用 source-map(如果有 Sentry 这类错误跟踪系统),开发环境使用 cheap-module-eval-source-map

上次编辑于:
贡献者: lingronghai