cssmagic / blog

CSS魔法 - 博客
http://blog.cssmagic.net/
2.8k stars 274 forks source link

[译] Webpack 2 有哪些新东西 #58

Open cssmagic opened 8 years ago

cssmagic commented 8 years ago

[译] Webpack 2 有哪些新东西

Work in progress... This reflects stuff until 2.0.5-beta+

仍在开发中……本文只反映截至 2.0.5-beta+ 版本的情况。

Major changes

主要变更

ES6 Modules

ES6 模块

webpack 2 brings native support ES6 Modules. This means webpack now understands import and export without them being transformed to CommonJS:

Webpack 2 将增加对 ES6 模块的原生支持。这意味着 Webpack 现在可以识别 importexport 了,不需要先把它们转换成 CommonJS 模块的格式:

import { currentPage, readPage } from "./book";

currentPage === 0;
readPage();
currentPage === 1;
// book.js
export var currentPage = 0;

export function readPage() {
    currentPage++;
}

export default "This is a book";

Code Splitting with ES6

遵循 ES6 的代码拆分方式

The ES6 Loader spec defines System.import as method to load ES6 Modules dynamically on runtime.

ES6 Loader 规范定义了 System.import 方法,用于在运行时动态加载 ES6 模块。

Webpack threads System.import as splitpoint and puts the requested module in a separate chunk.

Webpack 把 System.import 作为拆分点,然后把被请求的模块放入一个单独的 “块”(chunk)中。

System.import takes the module name as argument and returns a Promise.

System.import 接受模块名作为参数,然后返回一个 Promise。

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

Good news: Failure to load a chunk can be handled now.

顺便说个好消息:chunk 加载失败产生的错误现在可以被捕获了。

Dynamic expressions

动态表达式

It's possible to pass an partial expression to System.import. This is handled similar to expressions in CommonJS (webpack creates a context with all possible files).

还可以把一个表达式作为参数传给 System.import。表达式的处理方式类似于 CommonJS(Webpack 为每个可能的文件创建一个独立的上下文)。

System.import creates a separate chunk for each possible module.

System.import 会令每个可能的模块都产生一个独立的 chunk。

function route(path, query) {
    return System.import("./routes/" + path + "/route")
        .then(route => new route.Route(query));
}
// This creates a separate chunk for each possible route
// 这会为每种可能的路径组合都创建一个对应的 chunk。

Mixing ES6 with AMD and CommonJS

混用 ES6、AMD 和 CommonJS 模块

As for AMD and CommonJS you can freely mix all three module types (even within the same file). Webpack behaves similar to babel in this case:

至于 AMD 和 CommonJS,你可以自由地混用所有这三种模块类型(哪怕在是在同一个文件内)。Webpack 在这种情况下的行为跟 Babel 类似:

// CommonJS consuming ES6 Module
// 以 CommonJS 语法调用 ES6 模块
var book = require("./book");

book.currentPage;
book.readPage();
book.default === "This is a book";
// ES6 Module consuming CommonJS
// ES6 模块调用 CommonJS 模块
import fs from "fs"; // module.exports map to default
                     // module.exports 将映射为 default
import { readFileSync } from "fs"; // named exports are read from returned object+
                                   // 具名导出的方法将可以通过模块返回的对象来读取

typeof fs.readFileSync === "function";
typeof readFileSync === "function";

babel and webpack

Babel 与 Webpack

The es2015 babel preset transforms ES6 Modules to CommonJS by default. To use webpack to process ES6 Modules you should use the es2015-webpack preset instead.

在默认情况下,Babel 的 es2015 预设方案(preset)会把 ES6 模块转换为 CommonJS 格式。如果你想让 Webpack 来处理 ES6 模块,那你应该换用 es2015-webpack 这个预设方案。

ES6 specific optimizations

针对 ES6 规范的优化

The static nature of ES6 Modules allows some new kind of optimizations. In example in many cases it's possible to detect which exports are used and which aren't used.

ES6 模块与生俱来的静态特性允许我们采用一些新型的优化措施。比如说,在很多场景下,我们可以探测出哪些导出的接口会被用到,而哪些不会。

In cases in which webpack can say for sure that an export isn't used it omits the statement which exposes the export to other modules. Later the minimizer may flag the declaration as unused and omits it.

只要 Webpack 可以确定一个输出接口没有被别的模块用到,就会忽略那条输出语句。随后代码压缩工具就可以把那条声明标记为无用并丢弃。

In the following cases it's possible to detect usage:

在以下情况下,可以探测出接口的使用情况:

In the following cases it's not possible to detect usage:

而在以下情况下,无法探测出接口的使用情况:

ES6 export mangling

ES6 导出混淆

In cases where it's possible to track export usage, webpack can mangle export names to single char properties.

万一无法追踪导出接口的使用情况,Webpack 可以把导出的接口名混淆为单个字符的属性。(译注:抱歉,我不明白这句话。)

Configuration

配置

In the past environment variables are often used to handle different environments in the configuration file. Webpack 2 brings a new way to pass options to the configuration.

在过去的环境中,变量通常用来在配置文件中处理不同的环境。Webpack 2 引入了一种新方法,可以将选项传给配置。

The configuration file can export a function which returns the configuration. The function is called by the CLI and the value passed via --env is passed to the configuration function.

配置文件可以导出一个函数,这个函数返回配置。这个函数会被 CLI(命令行界面)调用,而通过 --env 参数传过来的值会被传递给这个配置函数。

You can pass a string (--env dev => "dev") or a complex options object (--env.minimize --env.server localhost => {minimize: true, server: "localhost"}). I would recommend using an object, because it's more extendable, but it's up to you.

你可以传递一个字符串(--env dev"dev"),或者一个复杂的选项对象(--env.minimize --env.server localhost{minimize: true, server: "localhost"})。我会推荐使用一个对象,因为那样更具扩展性,不过最后还是看你自己如何选择。

Example

示例代码

// webpack.config.babel.js
exports default function(options) {
    return {
        // ...
        devtool: options.dev ? "cheap-module-eval-source-map" : "hidden-source-map"
    };
}

Resolving options

解析选项

There was a major refactoring in the resolver (https://github.com/webpack/enhanced-resolve). This means the resolving option were changed too. Mostly simplification and changes that make it more unlikely to configure it incorrectly.

解析器有少量重构( https://github.com/webpack/enhanced-resolve )。这意味着解析选项也发生了变化。基本上是一些简化,以及变得更加不容易写错。

The new options are:

新选项如下:

{
    modules: [path.resolve(__dirname, "app"), "node_modules"]
    // (was split into `root`, `modulesDirectories` and `fallback` in the old options)
    // In which folders the resolver look for modules
    // relative paths are looked up in every parent folder (like node_modules)
    // absolute paths are looked up directly
    // the order is respected
    // (以前这个选项分散在 `root`、`modulesDirectories` 和 `fallback` 三处。)
    // 模块查找路径:指定解析器查找模块的目录。
    // 相对路径会在每一层父级目录中查找(类似 node_modules)。
    // 绝对路径会直接查找。
    // 将按你指定的顺序查找。

    descriptionFiles: ["package.json", "bower.json"],
    // These JSON files are read in directories
    // 描述文件:这些 JSON 文件将在目录中被读取。

    mainFields: ["main", "browser"],
    // These fields in the description files are looked up when trying to resolve the package directory
    // 入口字段:在解析一个包目录时,描述文件中的这些字段所指定的文件将被视为包的入口文件。

    mainFiles: ["index"]
    // These files are tried when trying to resolve a directory
    // 入口文件:在解析一个目录时,这些文件将被视为目录的入口文件。

    aliasFields: ["browser"],
    // These fields in the description files offer aliasing in this package
    // The content of these fields is an object where requests to a key are mapped to the corresponding value
    // 别名字段:描述文件中的这些字段提供了该包的别名对照关系。
    // 这些字段的内容是一个对象,每当请求某个键名时,就会映射到对应的键值。

    extensions: [".js", ".json"],
    // These extensions are tried when resolving a file
    // 扩展名:在解析一个文件时,将会尝试附加这些文件扩展名。

    enforceExtension: false,
    // If false it will also try to use no extension from above
    // 强制使用扩展名:如果值为 false,在解析一个文件时,也会尝试匹配无扩展名的文件。

    moduleExtensions: ["-loader"],
    // These extensions are tried when resolving a module
    // 模块后缀名:在解析一个模块名时,将会尝试附加这些后缀名。

    enforceModuleExtension: false,
    // If false it's also try to use no module extension from above
    // 强制使用模块后缀名:如果值为 false,在解析一个模块名时,也会尝试匹配不包含后缀名的模块。

    alias: {
        jquery: path.resolve(__dirname, "vendor/jquery-2.0.0.js")
    }
    // These aliasing is used when trying to resolve a module
    // 另外:在解析一个模块名时,会使用这个别名映射表。
}

Minor breaking changes

次要的破坏性变更

Promise polyfill

Promise 的 polyfill

The chunk loading stuff now relies on Promise being available. This means you need to provide a Promise polyfill for older browsers.

分块加载机制现在是依赖于 Promise 的。这表示你需要在旧版浏览器下提供一个 Promise 的 polyfill

The ES6 spec uses promises and I don't want to include a Promise polyfill in every bundle. So it's up the application developer to provide the polyfill if needed.

ES6 规范已经包含了 Promise,我不想在每个打包文件中都加入一个 Promise 的 polyfill。因此,如果需要的话,就要由应用的开发者来提供这个 polyfill 了。

Can I use Promises?

Promises 的兼容性情况 - CanIUse.com

Other polyfills

其它 polyfill

You need a Object.defineProperty polyfill for ES6 Module or if using the module object in other ways than module.exports, module.id, module.loaded or module.hot.

你需要一个 Object.defineProperty 的 polyfill 来实现 ES6 的模块特性。或者在以(除了 module.exportsmodule.idmodule.loadedmodule.hot 之外的)其它方式使用 module 对象时,这个 polyfill 也是需要的。

For ES6 Modules you also need a Function.prototype.bind polyfill.

为了实现 ES6 的模块特性,你还需要一个 Function.prototype.bind 的 polyfill。

That's not new but anyway: You need an Object.keys polyfill for require.context().keys().

最后这一条也不算新鲜了,但还是提一下吧:你需要一个 Object.keys 的 polyfill 来运行 require.context().keys()

Loaders configuration

Loader 的配置

The loaders in the configuration now match to the resourcePath instead of the resource. This means the query string is no longer included for matching.

现在,配置文件所指定的各个 loader 的值只匹配 resourcePath(资源路径),而不是以前的 resource(资源)。这表示 query string(查询字符串)不再参与匹配。

This was an issue with bootstrap which complicates the test for bootstrap fonts and images from /\.svg$/ to /\.svg($|\?)/. Now you can use the simple form.

以前在使用 Bootstrap 时有一个问题,会把 Bootstrap 字体和图片的 test 字段搞复杂——必须把 /\.svg$/ 写成 /\.svg($|\?)/。现在你就可以直接使用那个简单的形式了。

The loader in the configuration now resolves relative to the configuration file (or the context option in the configuration file if specified). This should fix some issues with npm linked modules that are outside of the current package.

配置文件所指定的各个 loader 现在是相对于配置文件进行解析的(但如果配置文件指定了 context 选项则以它为准)。在以前,如果某些外部模块是通过 npm link 链接到当前包的,则会产生问题,现在应该都可以解决了。

Another change allows the following syntax to configure loaders:

此外,我们将可以使用以下语法来配置 loader:

loaders: [
    {
        test: /\.css$/,
        loaders: [
            "style-loader",
            { loader: "css-loader", query: { modules: true } },
            {
                loader: "sass-loader",
                query: {
                    includePaths: [
                        path.resolve(__dirname, "some-folder")
                    ]
                }
            }
        ]
    }
]

Loader options & minimize

Loader 选项以及代码压缩

The UglifyJsPlugin no longer puts loaders into minimize mode. The debug option has been removed. Loaders should no longer read their options from the webpack configuration. Instead you need to provide these options with the LoaderOptionsPlugin.

UglifyJsPlugin 将不再把所有 loader 都切到代码压缩模式。debug 选项已经被移除。Loader 不应该再从 Webpack 的配置那里读取自己选项了。取而代之的是,你需要通过 LoaderOptionsPlugin 来提供这些选项。

new webpack.LoaderOptionsPlugin({
    test: /\.css$/, // optionally pass test, include and exclude, default affects all loaders
                    // 可以传入 test、include 和 exclude,默认会影响所有的 loader
    minimize: true,
    debug: false,
    options: {
        // pass stuff to the loader
        // 这里的选项会传给 loader
    }
})

This happens for separation of concern reasons. I want to disallow arbitrary keys in the configuration, to enable configuration validation.

决定使用这种设计是出于 “关注点分离” 的考虑。我希望在配置中禁止随意的字段名,以便让校验配置成为可能。

Plugins

插件

Many plugins now take option objects instead of multiple arguments. This happens because it is easier to extend. They throw an Error when the old argument style is passed.

现在许多插件将可以接受一个选项对象,而不是以前多个参数的形式。作为这个改动是因为这样更易于扩展。如果把旧版参数传给插件,插件会抛出一个错误。

HMR communication

“模块热替换” 的通信机制

In webpack 1 the update signal used the Web Messaging API (postMessage). Webpack 2 uses a standard event emitter to receive the event. This means WebSocket must be inline in the bundle.

在 Webpack 1 中,更新信号用的是 Web Messaging API(postMessage)。而 Webpack 2 将使用一个标准的事件触发器来传递事件信号。这表示 WebSocket 必须内联到打包文件中。

webpack-dev-server has inlined mode as default now.

webpack-dev-server 现在在默认情况下就处于内联模式。

This should allow to use the webpack-dev-server to update code in WebWorkers.

这应该使得我们可以用 webpack-dev-server 来更新 WebWorker 中的代码。

Occurrence order

出现顺序

The plugin is no longer needed and occurrence order is on by default.

OccurrenceOrderPlugin 这个插件将不再需要了,类似的功能默认就是开启的。

Code Splitting

代码拆分

require.ensure and AMD require is now always async, even if the chunk was already loaded.

require.ensure 和 AMD 的 require 的加载方式现在都是异步的了,哪怕所需的 chunk 已经加载了。


本文在 “CSS魔法” 微信公众号首发,扫码立即订阅:

weixin-qrcode


© Creative Commons BY-NC-ND 4.0   |   我要订阅   |   我要打赏

koakumaping commented 8 years ago

代码压缩功能不错

hkongm commented 8 years ago

postMessage改为包含WS,不明何故。

jxbb commented 8 years ago

ES6 导出混淆的意思可能是说可以在模块导出后,但是没有使用到,可以把导出的对象的属性名压缩掉。

cssmagic commented 8 years ago

@jxbb 嗯,有可能。我回头看 v2 的文档去。

Yunkou commented 8 years ago

现在慢还是个病垢,希望V2 能提高编译速度

JasinYip commented 8 years ago

Tree-shaking 没提啊……

hax commented 8 years ago

@JasinYip “针对 ES6 规范的优化”这节讲的就是 tree-shaking,只是没用这个术语。

mc-zone commented 8 years ago

顶一个,ES6 modules 已经等很久了。现在整个项目都是ES6,优化打包结果会小很多。

tuoxiansp commented 8 years ago

ES6 导出混淆

In cases where it's possible to track export usage, webpack can mangle export names to single char properties.

万一无法追踪导出接口的使用情况,Webpack 可以把导出的接口名混淆为单个字符的属性。(译注:抱歉,我不明白这句话。)

这里的意思是 webpack 在可以追踪导出的接口的使用情况时,可以把导出的接口名压缩成单字符属性。 即: export i_am_a_very_long_name;

import i_am_a_very_long_name from 'xxx';

混淆后:

export a;

import a from 'xxx';

cssmagic commented 8 years ago

@tuoxiansp 谢谢提示 :+1:

我译错了,我把 "possible" 看成 "impossible" 了。我回头改一下。

blade254353074 commented 8 years ago

注意 module.loaders 现在变成了 module.rules

  module: {
-   loaders: [
+   rules: [
      {
        test: /\.css$/,
-       loaders: [
+       use: [
          {
            loader: "style-loader"
          },
          {
            loader: "css-loader",
-           query: {
+           options: {
              modules: true
            }
        ]
      }
    ]
  }
blade254353074 commented 8 years ago

详情请看 How to Upgrade from Webpack 1?

cssmagic commented 8 years ago

@blade254353074 好的,谢谢提醒。回头更新。

xiaobinwu commented 7 years ago

mark

ImJoeHs commented 7 years ago

require.ensure and AMD require is now always async, even if the chunk was already loaded.

require.ensure 和 AMD 的 require 的加载方式现在都是异步的了,哪怕所需的 chunk 已经加载了。

请教一下这个是什么意思?不是一直都是异步的吗,我有点乱了--

hax commented 7 years ago

@ImJoeHs 仔细读原文,就知道意思是原本如果chunk已经加载,则调用require.ensure是同步的。

ImJoeHs commented 7 years ago

@hax 惭愧惭愧,有中文就下意识偷懒了不看原文,是我伸手党了。谢谢回答!

fa-ge commented 7 years ago

es2015-webpack预设没什么用啊,预设配置成["es2015", {"modules": false}]才有效果。

mc-zone commented 7 years ago

Promise polyfill

Promise 的 polyfill

The chunk loading stuff now relies on Promise being available. This means you need to provide a Promise polyfill for older browsers.

分块加载机制现在是依赖于 Promise 的。这表示你需要在旧版浏览器下提供一个 Promise 的 polyfill。

The ES6 spec uses promises and I don't want to include a Promise polyfill in every bundle. So it's up the application developer to provide the polyfill if needed.

ES6 规范已经包含了 Promise,我不想在每个打包文件中都加入一个 Promise 的 polyfill。因此,如果需要的话,就要由应用的开发者来提供这个 polyfill 了。

Can I use Promises?

Promises 的兼容性情况 - CanIUse.com

Other polyfills

其它 polyfill

前天2.2正式 release 了,关于 Polyfill 的问题之前自己写了个简单插件,现在放出来,欢迎试用 😄 :

https://github.com/mc-zone/webpack2-polyfill-plugin