三石的webpack.config.js(基础篇)

538 阅读6分钟

webpack.config系列中较为基础的部分,稍微高阶点的都会在 进阶篇

配置文件

一般情况下,webpack 的配置文件是 JavaScript 文件,文件内导出了一个 webpack 配置的对象

快速生成配置文件

npx webpack-cli init

使用不同的配置文件

// package.json
"scripts": {
  "build": "webpack --config prod.config.js"
}

使用其他编程语言编写配置文件

Configuration Languages

其他导出格式

我们知道,配置文件一般是导出一个对象,但也可以导出其他格式

导出函数

一般是用于区分开发/生产环境(只是其中的一个方法)。该函数包括两个参数,一是环境(参考environment 选项文档)、二是webpack配置项

module.exports = function(env, argv) {
+  return {
+    mode: env.production ? 'production' : 'development',
+    devtool: env.production ? 'source-map' : 'eval',
     plugins: [
       new TerserPlugin({
         terserOptions: {
+          compress: argv.mode === 'production' // only if `--mode production` was passed
         }
       })
     ]
+  };
};
导出Promise

当需要异步加载配置变量时,webpack 将执行函数并导出一个配置文件,同时返回一个 Promise

module.exports = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        entry: './app.js',
        /* ... */
      });
    }, 5000);
  });
};

只有通过 webpack 命令行工具返回的 Promise 才生效

导出多个配置

可以导出多种配置,当运行 webpack 时,所有配置项都会构建。比如,对于多 targets(如 AMD 和 CommonJS)构建 library时会非常有用

module.exports = [
  {
    output: {
      filename: './dist-amd.js',
      library: {
        name: 'amd',
        type: 'amd',
      }
    },
    name:'amd',
    entry: './app.js',
    mode: 'production',
  },
  {
    output: {
      filename: './dist-commonjs.js',
      library: {
        name: 'commonjs',
        type: 'commonjs',
      }
    },
    name:'commonjs',
    entry: './app.js',
    mode: 'production',
  },
];

如果你只传了一个--config-name名字标识,webpack 将只会构建指定的配置项

  1. 如果你的某个配置依赖于另一个配置的输出,你可以使用一个dependencies列表指定一个依赖列表
module.exports = [
  {
    name: 'client',
    target: 'web',
    // …
  },
  {
    name: 'server',
    target: 'node',
    dependencies: ['client'],
  },
];
  1. 如果你导出了多个配置,你可以在配置中使用 parallelism 选项来指定编译的最大并发数
module.exports = [
  {
    //config-1
  },
  {
    //config-2
  },
];
module.exports.parallelism = 1;

Notice

  • 整个配置中我们使用 Node 内置的 path 模块,并在它前面加上 __dirname 这个全局变量。可以防止不同操作系统之间的文件路径问题,并且可以使相对路径按照预期工作

name

配置的名称。当加载不同的配置时会被使用

context

基础目录,绝对路径,用于从配置中解析入口点(entry point)和 加载器(loader)
默认是Node.js 进程的当前工作目录

target

作用

由于 JavaScript 既可以编写服务端代码也可以编写浏览器代码,所以 webpack 提供了多种部署 target
默认值为 "browserslist",如果没有找到 browserslist 的配置,则默认为 "web"

设置

module.exports = {
  target: 'node',
};

示例中,target 设置为 node,webpack 将在类 Node.js 环境编译代码。(使用 Node.js 的 require 加载 chunk,而不加载任何内置模块,如 fs 或 path)。

每个 target 都包含各种 deployment(部署)/environment(环境)特定的附加项,以满足其需求。 可指定 node 或者 electron 的版本,如'node12.18'

多target

首先,个人不太推荐这么用!
当传递多个目标时,将使用共同的特性子集,也就是说,要它们都拥有的特性,才能够使用

下例中,Webpack 将生成 web 平台的运行时代码,并且只使用 ES5 相关的特性。

module.exports = {
  // ...
  target: ['web', 'es5'],
};

注意:目前并不是所有的 target 都可以进行混合。

mode

打包模式,默认是生产模式,生产模式下会做代码压缩,摇树等操作。

  • 设置 mode 后改变全局变量(业务js中也可以使用process.env.NODE_ENV 的值
  • 设置 mode 后等同于在中加入了webpack.DefinePlugin插件
module.exports = function(env, argv) {
+    mode: env.production ? 'production' : 'development',
-    devtool: env.production ? 'source-map' : 'eval',
-    plugins: [
-      new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development|production") })
-     ]
+  };
};

mode可以在 webpack.config.js 里配置,也可以通过CLI参数传递,如 webpack --mode=development

选项

有三个可选值,分别是"none","development","production",默认为 "production"

  • none:不开启任何优化选项
  • development:设置后会启用 NamedChunksPlugin 和 NamedModulesPlugin,也就是这些插件,webpack 里就不用写了
module.exports = function(env, argv) {
+    mode: 'development',
-    devtool:  'eval',
-    plugins: [
-      new webpack.NamedChunksPlugin(),
-      new webpack.NamedModulesPlugin(),
-      new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") })
-     ]
+  };
};

NamedModulesPlugin:原本webpack并不会给打包的模块加上姓名,一般都是按照序号来,从0开始,然后加载第几个模块。没有NamedModulesPlugin,模块就是一个数组,引用也是按照在数组中的顺序引用,新增减模块都会导致序号的变化。有了NamedModulesPlugin,模块都拥有了姓名,而且都是独一无二的key,不管新增减多少模块,模块的key都是固定的

NamedChunksPlugin:与 NamedModulesPlugin 相似,原来的chunks也是序号命名,开启这个plugin后会有独一无二的命名

  • production:设置后会启用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin(也就是webpack里不用再设置了!)

UglifyJsPlugin :混淆/压缩JS,并删除未引用代码,并压缩。不过这个可以在optimization.minimize设置

ModuleConcatenationPlugin :作用域提升。此插件仅适用于由 webpack 直接处理的 ES6 模块。在使用转译器时,你需要禁用对模块的处理(例如 Babel 中的 modules 选项)

NoEmitOnErrorsPlugin :在输出阶段时,遇到编译错误是否跳过

FlagIncludedChunksPlugin:防止chunks多次加载

OccurrenceOrderPlugin :按照chunk引用次数来安排出现顺序,因为这让经常引用的模块和chunk拥有更小的id

SideEffectsFlagPlugin :副作用是否打包,优化块会讲

Notice

如果要根据 webpack.config.js 中的 mode 变量更改打包行为,则必须将配置导出为函数,而不是导出对象

var config = {
  entry: './app.js',
  //...
};

module.exports = (env, argv) => {
  if (argv.mode === 'development') {
    config.devtool = 'source-map';
  }

  if (argv.mode === 'production') {
    //...
  }

  return config;
};

这样就可以通过cli中 mode 参数来传入变量 "build": "webpack --mode=production"

entry

  • 字符串
module.exports = {
  entry: './src/file.js',
};
  • 数组
module.exports = {
  entry: ['./src/file_1.js', './src/file_2.js'],
};
  • 对象 entry 对象里的key,将作为 name 传入 output中。
    在下列例子中,假设 home.js 和 main.js 都依赖 'react', 'react-dom',如果单纯进行入口隔离(即两个entry home 和 main),那么打包出的bundle都会有一份 'react', 'react-dom'。所以我们将 'react', 'react-dom'抽离出来共享:
module.exports = {
  entry: {
    home: {
      import: './home.js',
      dependOn: 'shared',
    },,
    shared: ['react', 'react-dom'],
    main: {
      import: './main.js',
      filename: 'pages/personal.js',
      dependOn: 'shared',
      chunkLoading: 'jsonp',
      asyncChunks: true, // Create async chunks that are loaded on demand.
      layer: 'name of layer', // set the layer for an entry point
    },
  },
};

filename

默认情况下,入口 chunk 的输出文件名是从 output.filename 中提取出来的,但你可以为特定的入口指定一个自定义的输出文件名,如上面的 filename: 'pages/personal.js',

dependOn

默认情况下,每个入口 chunk 保存了全部其用的模块(modules)。使用 dependOn 选项你可以与另一个入口 chunk 共享模块,如上面的 dependOn: 'shared',这样,personal 这个chunk 就不会包含 shared拥有的模块了
dependOn 选项也可以为数组 dependOn: ['shared', 'shared2']

动态entry

如果传入一个函数,那么它将会在每次 make (webpack启动和监听文件变化时,make事件都会触发) 事件中被调用

module.exports = {
  //...
  entry: () => './demo',
  // or下面这种
  // entry: () => new Promise((resolve) => resolve(['./demo', './demo2'])),

};

应用场景

分离 app(应用程序) 和 vendor(第三方库) 入口
这样你就可以在 vendor.js 中存入未做修改的必要 library 或文件(例如 Bootstrap, jQuery, 图片等),然后将它们打包在一起成为单独的 chunk。内容哈希保持不变,这使浏览器可以独立地缓存它们,从而减少了加载时间。这是webpack4以下版本常用做法,webpack4以上请使用optimization.splitChunks

webpack.config.js

module.exports = {
  entry: {
    main: './src/app.js',
    vendor: './src/vendor.js',
  },
};

webpack.prod.js

module.exports = {
  output: {
    filename: '[name].[contenthash].bundle.js',
  },
};

webpack.dev.js

module.exports = {
  output: {
    filename: '[name].bundle.js',
  },
};

多页面应用程序
在多页面应用程序中,每个 HTML 页面都有一个入口起点

module.exports = {
  entry: {
    pageOne: './src/pageOne/index.js',
    pageTwo: './src/pageTwo/index.js',
    pageThree: './src/pageThree/index.js',
  },
};

Output

三石的webpack.config.js(output篇)

Plugin

三石的webpack(Plugins篇)