Webpack:从 v1 升级到 v2 或 v3

2023-06-01 15:00 更新

以下各节描述从 webpack 1 到 webpack 2 的重大变化。

resolve.root, resolve.fallback, resolve.modulesDirectories

这些选项被一个单独的选项 ​resolve.modules​ 取代。更多用法请查看 解析 。

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

resolve.extensions

此选项不再需要传一个空字符串。此行为被迁移到 ​resolve.enforceExtension​ 。更多用法请查看 解析 。

resolve.*

这里更改了几个 API 。由于不常用,不在这里详细列出。更多用法请查看 解析 。

module.loaders is now module.rules

旧的 loader 配置被更强大的 rules 系统取代,后者允许配置 loader 以及其他更多选项。 为了兼容旧版,​module.loaders​ 语法仍然有效,旧的属性名依然可以被解析。 新的命名约定更易于理解,并且是升级配置使用 ​module.rules​ 的好理由。

  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: {
          // ...
        }
      }
    ]
  }

链式 loaders

就像在 webpack 1 中,loader 可以链式调用,上一个 loader 的输出被作为输入传给下一个 loader。 使用 rule.use 配置选项,​use​ 可以设置为一个 loader 数组。 在 webpack 1 中,loader 通常被用 ​!​ 连写。这一写法在 webpack 2 中只在使用旧的选项 ​module.loaders​ 时才有效。

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

已移除 ​-loader​ 模块名称自动扩展

在引用 loader 时,不能再省略 ​-loader​ 后缀了:

  module: {
    rules: [
      {
        use: [
-         "style",
+         "style-loader",
-         "css",
+         "css-loader",
-         "less",
+         "less-loader",
        ]
      }
    ]
  }

你仍然可以通过配置 ​resolveLoader.moduleExtensions​ 配置选项,启用这一旧有行为,但是我们不推荐这么做。

+ resolveLoader: {
+   moduleExtensions: ["-loader"]
+ }

json-loader 不再需要手动添加

如果没有为 JSON 文件配置 loader,webpack 将自动尝试通过 [​json-loader​] 加载 JSON 文件。

  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 方案了:

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

module.preLoaders and module.postLoaders were removed:

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

UglifyJsPlugin sourceMap

UglifyJsPlugin​ 的 ​sourceMap​ 选项现在默认为 ​false​ 而不是 ​true​。这意味着如果你在压缩代码时启用了 ​source map​,或者想要让 ​uglifyjs​ 的警告能够对应到正确的代码行,你需要将 ​UglifyJsPlugin​ 的 ​sourceMap​ 设为 ​true​ 。

  devtool: "source-map",
  plugins: [
    new UglifyJsPlugin({
+     sourceMap: true
    })
  ]

UglifyJsPlugin warnings

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

UglifyJsPlugin minimize loaders

UglifyJsPlugin​ 不再压缩 loaders。在未来很长一段时间里,需要通过设置 ​minimize:true​ 来压缩 loaders。参考 loader 文档里的相关选项。

loaders 的压缩模式将在 webpack 3 或后续版本中取消。

为了兼容旧的 loaders,loaders 可以通过插件来切换到压缩模式:

  plugins: [
+   new webpack.LoaderOptionsPlugin({
+     minimize: true
+   })
  ]

DedupePlugin has been removed

不再需要 ​webpack.optimize.DedupePlugin​。请从配置中移除。

BannerPlugin - 破坏性改动

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

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

默认加载 OccurrenceOrderPlugin

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

  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

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})

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

全动态 require 现在默认会失败

只有一个表达式的依赖(例如 ​require(expr)​)将创建一个空的 context 而不是一个完整目录的 context。

这样的代码应该进行重构,因为它不能与 ES2015 模块一起使用。如果你确定不会有 ES2015 模块,你可以使用 ​ContextReplacementPlugin​ 来指示 compiler 进行正确的解析。

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

如果你之前滥用 CLI 来传自定义参数到配置中,比如:

webpack --custom-stuff

// webpack.config.js
var customStuff = process.argv.indexOf('--custom-stuff') >= 0;
/* ... */
module.exports = config;

你将会发现新版中不再允许这么做。CLI 现在更加严格了。

替代地,现在提供了一个接口来传递参数给配置。我们应该采用这种新方式,在未来许多工具将可能依赖于此。

webpack --env.customStuff

module.exports = function (env) {
  var customStuff = env.customStuff;
  /* ... */
  return config;
};

详见 CLI 。

require.ensure and AMD require are asynchronous

现在这些函数总是异步的,而不是当 chunk 已经加载完成的时候同步调用它们的回调函数 (callback) 。

require.ensure​ 现在依赖原生的 ​Promises​ 。如果不支持 Promise 的环境中使用 ​require.ensure​ ,你需要添加 polyfill. 。

通过 options 中配置 loader

你 不能再 通过 ​webpack.config.js​ 的自定义属性来配置 loader 。只能通过 ​options​ 来配置。下面配置的 ​ts​ 属性在 webpack 2 下不再有效:

module.exports = {
  //...
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'ts-loader',
      },
    ],
  },
  // 在 webpack 2 中无效
  ts: { transpileOnly: false }
};

什么是 ​options​ ?

好问题。严格来说,有两种办法,都可以用来配置 webpack 的 loader。典型的 ​options​ 被称为 ​query​,是一个可以被添加到 loader 名之后的字符串。它比较像一个查询字符串(query string),但是实际上有更强大的能力:

module.exports = {
  //...
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'ts-loader?' + JSON.stringify({ transpileOnly: false }),
      },
    ],
  },
};

不过它也可以分开来,写成一个单独的对象,紧跟在 loader 属性后面:

module.exports = {
  //...
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: 'ts-loader',
        options: { transpileOnly: false },
      },
    ],
  },
};

LoaderOptionsPlugin context

有的 loader 需要从配置中读取一些 context 信息。在未来很长一段时间里,这将需要通过 loader options 传入。详见 loader 文档的相关选项。

为了保持对旧 loaders 的兼容,这些信息可以通过插件传进来:

  plugins: [
+   new webpack.LoaderOptionsPlugin({
+     options: {
+       context: __dirname
+     }
+   })
  ]

debug

在 webpack 1 中 ​debug​ 选项可以将 loader 切换到调试模式 (debug mode) 。在未来很长一段时间里,这将需要通过 loader 选项传递。详见 loader 文档的相关选项。

loaders 的调试模式将在 webpack 3 或后续版本中取消。

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

- debug: true,
  plugins: [
+   new webpack.LoaderOptionsPlugin({
+     debug: true
+   })
  ]

ES2015 的代码分割

在 webpack 1 中,可以使用 ​require.ensure()​ 作为实现应用程序的懒加载 chunks 的一种方法:

require.ensure([], function (require) {
  var foo = require('./module');
});

ES2015 模块加载规范定义了 ​import()​ 方法,可以在运行时 (runtime) 动态地加载 ES2015 模块。webpack 将 ​import()​ 作为分割点 (split-point) 并将所要请求的模块 (requested module) 放置到一个单独的 chunk 中。​import()​ 接收模块名作为参数,并返回一个 Promise 。

function onClick() {
  import('./module')
    .then((module) => {
      return module.default;
    })
    .catch((err) => {
      console.log('Chunk loading failed');
    });
}

好消息是:如果加载 chunk 失败,我们现在可以进行处理,因为现在它基于 ​Promise​。

动态表达式

可以传递部分表达式给 ​import()​ 。这与 CommonJS 对表达式的处理方式一致(webpack 为所有可能匹配的文件创建 context](/plugins/context-replacement-plugin/)) )。

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

function route(path, query) {
  return import(`./routes/${path}/route`).then(
    (route) => new route.Route(query)
  );
}
// 上面代码为每个可能的路由创建独立的 chunk

混合使用 ES2015、AMD 和 CommonJS

你可以自由混合使用三种模块类型(甚至在同一个文件中)。在这个情况中 webpack 的行为和 babel 以及 node-eps 一致:

// CommonJS 调用 ES2015 模块
var book = require('./book');

book.currentPage;
book.readPage();
book.default === 'This is a book';
// ES2015 模块调用 CommonJS
import fs from 'fs'; // module.exports 映射到 default
import { readFileSync } from 'fs'; // 从返回对象(returned object+)中读取命名的导出方法(named exports)

typeof fs.readFileSync === 'function';
typeof readFileSync === 'function';

值得注意的是,你需要让 Babel 不解析这些模块符号,从而让 webpack 可以使用它们。你可以通过设置如下配置到 .babelrc 或 babel-loader 来实现这一点。

.babelrc

{
  "presets": [["es2015", { "modules": false }]]
}

Hints

不需要改变什么,但有机会改变。

模版字符串

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

- require("./templates/" + name);
+ require(`./templates/${name}`);

配置中使用 Promise

webpack 现在支持在配置文件中返回 ​Promise​ 了。这让你能在配置文件中做异步处理。

webpack.config.js

module.exports = function () {
  return fetchLangs().then((lang) => ({
    entry: '...',
    // ...
    plugins: [new DefinePlugin({ LANGUAGE: lang })],
  }));
};

高级 loader 匹配

webpack 现在支持对 loader 进行更多方式的匹配。

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​ 显示一个模块中被使用的 exports 信息。

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

-p​ 能够定义 ​process.env.NODE_ENV​ 为 ​"production"​ 。

Loader 变更

以下变更仅影响 loader 的开发者。

Cacheable

Loaders 现在默认可被缓存。Loaders 如果不想被缓存,需要选择不被缓存。

  // 缓存 loader
  module.exports = function(source) {
-   this.cacheable();
    return source;
  }
  // 不缓存 loader
  module.exports = function(source) {
+   this.cacheable(false);
    return source;
  }

复杂 options

webpack 1 只支持能够「可 ​JSON.stringify​ 的对象」作为 loader 的 options。

webpack 2 现在支持任意 JS 对象作为 loader 的 options。

webpack 2.2.1 之前(即从 2.0.0 到 2.2.0),使用复合 ​options​ ,需要在 ​options​ 对象上添加 ​ident​,允许它能够被其他 loader 引用。这 在 2.2.1 中被删除 ,因此目前的迁移不再需要使用 ​ident​ 键。

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


以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号