知海

从 webpack 3 迁移

webpackjsorg-main迁移指南

本指南介绍了从 webpack 1 迁移到 webpack 2 的主要变化。

T> 需要注意的是,从 webpack 2 到 webpack 3 的变化要小得多,因此迁移过程应该不会太复杂。如果遇到问题,请参阅更新日志了解详情。

resolve.root、resolve.fallback、resolve.modulesDirectories

这些选项已被 resolve.modules 这一个选项取代。更多用法请参见解析

diff 复制代码
  resolve: {
-   root: path.join(__dirname, "src")
+   modules: [
+     path.join(__dirname, "src"),
+     "node_modules"
+   ]
  }

resolve.extensions

此选项不再需要传入空字符串。此行为已移至 resolve.enforceExtension。更多用法请参见解析

resolve.*

这里的一些 API 发生了变化。由于使用不广泛,此处不再详细列出。详情请参见解析

module.loaders 现在是 module.rules

旧的 loader 配置已被更强大的 rules 系统取代,该系统允许配置 loader 及更多内容。
出于兼容性考虑,旧的 module.loaders 语法仍然有效,并且旧的名称仍会被解析。
新的命名约定更易于理解,也是升级配置以使用 module.rules 的一个很好的理由。

diff 复制代码
  module: {
-   loaders: [
+   rules: [
      {
        test: /\.css$/,
-       loaders: [
-         "style-loader",
-         "css-loader?modules=true"
+       use: [
+         {
+           loader: "style-loader"
+         },
+         {
+           loader: "css-loader",
+           options: {
+             modules: true
+           }
+         }
        ]
      },
      {
        test: /\.jsx$/,
        loader: "babel-loader", // 此处不要使用 "use"
        options: {
          // ...
        }
      }
    ]
  }

链式 loader

与 webpack 1 类似,loader 可以链式调用,以将结果从一个 loader 传递给下一个 loader。通过 rule.use 配置选项,use 可以设置为一个 loader 数组。
在 webpack 1 中,loader 通常使用 ! 进行链式调用。这种风格仅在旧的 module.loaders 选项下受支持。

diff 复制代码
  module: {
-   loaders: [{
+   rules: [{
      test: /\.less$/,
-     loader: "style-loader!css-loader!less-loader"
+     use: [
+       "style-loader",
+       "css-loader",
+       "less-loader"
+     ]
    }]
  }

自动添加 -loader 模块名扩展已被移除

引用 loader 时,不能再省略 -loader 扩展名:

diff 复制代码
  module: {
    rules: [
      {
        use: [
-         "style",
+         "style-loader",
-         "css",
+         "css-loader",
-         "less",
+         "less-loader",
        ]
      }
    ]
  }

你仍然可以通过 resolveLoader.moduleExtensions 配置选项选择旧行为,但不推荐这样做。

diff 复制代码
+ resolveLoader: {
+   moduleExtensions: ["-loader"]
+ }

此变更的原因请参见 #2986

不再需要 json-loader

当没有为 JSON 文件配置 loader 时,webpack 将自动尝试使用 json-loader 加载 JSON 文件。

diff 复制代码
  module: {
    rules: [
-     {
-       test: /\.json/,
-       loader: "json-loader"
-     }
    ]
  }

我们决定这样做是为了消除 webpack、node.js 和 browserify 之间的环境差异。

配置中的 loader 相对于 context 解析

webpack 1 中,配置的 loader 相对于匹配的文件进行解析。然而,在 webpack 2 中,配置的 loader 相对于 context 选项进行解析。

这解决了在使用 npm link 或引用 context 外部模块时,由 loader 引起的重复模块问题。

你可以移除一些用于绕过此问题的 hack:

diff 复制代码
  module: {
    rules: [
      {
        // ...
-       loader: require.resolve("my-loader")
+       loader: "my-loader"
      }
    ]
  },
  resolveLoader: {
-   root: path.resolve(__dirname, "node_modules")
  }

module.preLoaders 和 module.postLoaders 已被移除

diff 复制代码
  module: {
-   preLoaders: [
+   rules: [
      {
        test: /\.js$/,
+       enforce: "pre",
        loader: "eslint-loader"
      }
    ]
  }

UglifyJsPlugin sourceMap

UglifyJsPluginsourceMap 选项现在默认为 false,而不是 true。这意味着,如果你对压缩后的代码使用 source map,或者希望为 uglifyjs 警告提供正确的行号,则需要为 UglifyJsPlugin 设置 sourceMap: true

diff 复制代码
  devtool: "source-map",
  plugins: [
    new UglifyJsPlugin({
+     sourceMap: true
    })
  ]

UglifyJsPlugin 警告

UglifyJsPlugincompress.warnings 选项现在默认为 false,而不是 true
这意味着,如果你想看到 uglifyjs 警告,则需要将 compress.warnings 设置为 true

diff 复制代码
  devtool: "source-map",
  plugins: [
    new UglifyJsPlugin({
+     compress: {
+       warnings: true
+     }
    })
  ]

UglifyJsPlugin 压缩 loaders

UglifyJsPlugin 不再将 loader 切换到压缩模式。从长远来看,需要通过 loader 选项传递 minimize: true 设置。有关相关选项,请参阅 loader 文档。

loader 的压缩模式将在 webpack 3 或更高版本中移除。

为了保持与旧 loaders 的兼容性,可以通过插件将 loaders 切换到压缩模式:

diff 复制代码
  plugins: [
+   new webpack.LoaderOptionsPlugin({
+     minimize: true
+   })
  ]

DedupePlugin 已被移除

webpack.optimize.DedupePlugin 不再是必需的。请将其从配置中移除。

BannerPlugin - 重大变更

BannerPlugin 不再接受两个参数,而是接受一个单独的 options 对象。

diff 复制代码
  plugins: [
-    new webpack.BannerPlugin('Banner', {raw: true, entryOnly: true});
+    new webpack.BannerPlugin({banner: 'Banner', raw: true, entryOnly: true});
  ]

OccurrenceOrderPlugin 现在默认启用

OccurrenceOrderPlugin 现在默认启用,并且已被重命名(webpack 1 中为 OccurenceOrderPlugin)。
因此,请确保从配置中移除该插件:

diff 复制代码
  plugins: [
    // webpack 1
-   new webpack.optimize.OccurenceOrderPlugin()
    // webpack 2
-   new webpack.optimize.OccurrenceOrderPlugin()
  ]

ExtractTextWebpackPlugin - 重大变更

ExtractTextPlugin 需要版本 2 才能与 webpack 2 配合使用。

npm install --save-dev extract-text-webpack-plugin

此插件的配置更改主要是语法上的。

ExtractTextPlugin.extract

diff 复制代码
module: {
  rules: [
    {
      test: /.css$/,
-      loader: ExtractTextPlugin.extract("style-loader", "css-loader", { publicPath: "/dist" })
+      use: ExtractTextPlugin.extract({
+        fallback: "style-loader",
+        use: "css-loader",
+        publicPath: "/dist"
+      })
    }
  ]
}

new ExtractTextPlugin({options})

diff 复制代码
plugins: [
-  new ExtractTextPlugin("bundle.css", { allChunks: true, disable: false })
+  new ExtractTextPlugin({
+    filename: "bundle.css",
+    disable: false,
+    allChunks: true
+  })
]

完全动态的 require 现在默认失败

仅包含表达式的依赖(例如 require(expr))现在将创建一个空的 context,而不是整个目录的 context。

这样的代码应该被重构,因为它将无法与 ES2015 模块一起工作。如果无法重构,你可以使用 ContextReplacementPlugin 来提示编译器进行正确的解析。

?> 链接到一篇关于动态依赖的文章。

在 CLI 和配置中使用自定义参数

如果你滥用 CLI 向配置传递自定义参数,如下所示:

webpack --custom-stuff

js 复制代码
// webpack.config.js
const customStuff = process.argv.includes("--custom-stuff");

/* ... */
module.exports = config;

你可能会注意到这不再被允许。CLI 现在更加严格。

相反,现在有一个接口用于向配置传递参数。你应该使用这个接口。未来的工具可能会依赖于此。

webpack --env.customStuff

js 复制代码
module.exports = function (env) {
  const { customStuff } = env;
  /* ... */
  return config;
};

请参见 CLI

require.ensure 和 AMD require 是异步的

这些函数现在始终是异步的,而不会在 chunk 已加载时同步调用其回调。

require.ensure 现在依赖于原生的 Promise。如果在缺少 Promise 的环境中使用 require.ensure,则需要提供一个 polyfill。

Loader 配置通过 options 进行

不再可以webpack.config.js 中使用自定义属性来配置 loader。它必须通过 options 来完成。以下带有 ts 属性的配置在 webpack 2 中不再有效:

js 复制代码
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: "ts-loader",
      },
    ],
  },
  // 不适用于 webpack 2
  ts: { transpileOnly: false },
};

什么是 options

问得好。严格来说,它可能指的是两件事;都是配置 webpack loader 的方式。传统上,options 被称为 query,是一个可以附加到 loader 名称后面的字符串。很像查询字符串,但实际上具有更强大的功能

js 复制代码
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: `ts-loader?${JSON.stringify({ transpileOnly: false })}`,
      },
    ],
  },
};

但它也可以是一个单独指定的对象,与 loader 一起提供:

js 复制代码
module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: "ts-loader",
        options: { transpileOnly: false },
      },
    ],
  },
};

LoaderOptionsPlugin context

一些 loader 需要上下文信息并从配置中读取它们。从长远来看,这需要通过 loader 选项传递。有关相关选项,请参阅 loader 文档。

为了保持与旧 loaders 的兼容性,可以通过插件传递此信息:

diff 复制代码
  plugins: [
+   new webpack.LoaderOptionsPlugin({
+     options: {
+       context: __dirname
+     }
+   })
  ]

debug

debug 选项在 webpack 1 中将 loader 切换到调试模式。从长远来看,这需要通过 loader 选项传递。有关相关选项,请参阅 loader 文档。

loader 的调试模式将在 webpack 3 或更高版本中移除。

为了保持与旧 loaders 的兼容性,可以通过插件将 loaders 切换到调试模式:

diff 复制代码
- debug: true,
  plugins: [
+   new webpack.LoaderOptionsPlugin({
+     debug: true
+   })
  ]

使用 ES2015 进行代码分割

在 webpack 1 中,你可以使用 require.ensure() 作为为应用程序懒加载 chunk 的方法:

js 复制代码
require.ensure([], (require) => {
  const foo = require("./module");
});

ES2015 Loader 规范定义了 import() 作为在运行时动态加载 ES2015 模块的方法。Webpack 将 import() 视为一个分割点,并将请求的模块放入一个单独的 chunk 中。import() 接受模块名作为参数并返回一个 Promise。

js 复制代码
function onClick() {
  import("./module")
    .then((module) => module.default)
    .catch((err) => {
      console.log("Chunk loading failed");
    });
}

好消息:由于 chunk 加载是基于 Promise 的,现在可以处理加载失败的情况了。

动态表达式

可以向 import() 传递一个部分表达式。这类似于 CommonJS 中表达式的处理方式(webpack 会为所有可能的文件创建一个 context)。

import() 会为每个可能的模块创建一个单独的 chunk。

js 复制代码
function route(path, query) {
  return import(`./routes/${path}/route`).then(
    (route) => new route.Route(query),
  );
}
// 这会为每个可能的路由创建一个单独的 chunk

混合使用 ES2015、AMD 和 CommonJS

至于 AMD 和 CommonJS,你可以自由地混合使用这三种模块类型(甚至可以在同一个文件中)。在这种情况下,Webpack 的行为类似于 babel 和 node-eps:

js 复制代码
// CommonJS 消费 ES2015 模块
const book = require("./book");

book.currentPage;
book.readPage();
book.default === "This is a book";
js 复制代码
// ES2015 模块消费 CommonJS
import fs from "node:fs"; // module.exports 映射到 default

typeof fs.readFileSync === "function";
js 复制代码
// ES2015 模块消费 CommonJS
import { readFileSync } from "node:fs"; // 命名导出从返回的对象中读取+

typeof readFileSync === "function";

需要注意的是,你需要告诉 Babel 不要解析这些模块符号,以便 webpack 可以使用它们。你可以通过在 .babelrcbabel-loader 选项中设置以下内容来实现。

.babelrc

json 复制代码
{
  "presets": [["es2015", { "modules": false }]]
}

提示

无需更改,但提供了一些机会

模板字符串

Webpack 现在支持在表达式中使用模板字符串。这意味着你可以在 webpack 构造中开始使用它们:

diff 复制代码
- require("./templates/" + name);
+ require(`./templates/${name}`);

配置 Promise

Webpack 现在支持从配置文件返回一个 Promise。这允许在配置文件中进行异步处理。

webpack.config.js

js 复制代码
module.exports = function () {
  return fetchLangs().then((lang) => ({
    entry: "...",
    // ...
    plugins: [new DefinePlugin({ LANGUAGE: lang })],
  }));
};

高级 loader 匹配

Webpack 现在支持更多用于匹配 loader 的方式。

js 复制代码
module.exports = {
  // ...
  module: {
    rules: [
      {
        resource: /filename/, // 匹配 "/path/filename.js"
        resourceQuery: /^\?querystring$/, // 匹配 "?querystring"
        issuer: /filename/, // 如果从 "/path/filename.js" 请求,则匹配 "/path/something.js"
      },
    ],
  },
};

更多 CLI 选项

有一些新的 CLI 选项供你使用:

--define process.env.NODE_ENV="production" 请参见 DefinePlugin

--display-depth 显示每个模块到入口点的距离。

--display-used-exports 显示关于模块中使用了哪些导出信息。

--display-max-modules 设置输出中显示的模块数量(默认为 15)。

-p 现在也会将 process.env.NODE_ENV 定义为 "production"

Loader 变更

仅与 loader 作者相关的变更。

Cacheable

Loaders 现在默认是可缓存的。如果 loader 不可缓存,则必须选择退出。

diff 复制代码
  // 可缓存的 loader
  module.exports = function(source) {
-   this.cacheable();
    return source;
  }
diff 复制代码
  // 不可缓存的 loader
  module.exports = function(source) {
+   this.cacheable(false);
    return source;
  }

复杂选项

webpack 1 仅支持可通过 JSON.stringify 序列化的 loader 选项。

webpack 2 现在支持任何 JS 对象作为 loader 选项。

在 webpack 2.2.1 之前(即从 2.0.0 到 2.2.0),使用复杂选项需要为 options 对象使用 ident,以便其他 loader 可以引用它。这在 2.2.1 中已被移除,因此当前的迁移不需要使用任何 ident 键。

diff 复制代码
{
  test: /\.ext/
  use: {
    loader: '...',
    options: {
-     ident: 'id',
      fn: () => require('./foo.js')
    }
  }
}

帮助我们改进文档

发现翻译问题或内容错误?请告诉我们。