女王控的博客

Webpack3代码拆分

翻译转载:Webpack 3, Dynamic Imports, Code Splitting, and Long Term Caching… Made Easy. 在与 Webpack 1.x 升级到 3.x(比我想承认的更长的时间)后,突然点击了一下。看到揭开的奥秘:

演示一个简单的应用程序动态加载多个路线。请注意编辑“页面2”后的200响应,而未更改的文件则为304。

TL:DR 示例应用程序回购

js 复制代码
// webpack.config.js
const webpack = require('webpack');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const APP_DIR = path.resolve(__dirname, './src');
const MODULES_DIR = path.resolve(__dirname, './node_modules');

const package = require('./package.json');

module.exports = {
  devServer: {
    historyApiFallback: true
  },
  entry: {
    app: APP_DIR + '/index.js',
    vendor: Object.keys(package.dependencies)
  },
  output: {
    publicPath: '/',
    chunkFilename: '[name].[chunkhash].js',
    filename: '[name].[chunkhash].js'
  },
  resolve: {
    extensions: ['.js', '.jsx'],
    modules: [APP_DIR, MODULES_DIR]
  },
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        include: APP_DIR,
        use: {
          loader: 'babel-loader'
        }
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'Caching and Code Splitting',
      template: APP_DIR + '/index.html'
    }),
    new webpack.HashedModuleIdsPlugin(),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      filename: 'vendor.[chunkhash].js'
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'manifest'
    })
  ]
};
javaScript 复制代码
// .babelrc
{
  "presets": [
    "react",
    ["env", {
      "targets": {
        "browsers": ["last 2 versions"]
      }
    }],
    "stage-0"
  ],
  "comments": true
}
js 复制代码
{
  "name": "webpack-cs-ltc",
  "version": "1.0.0",
  "description": "A simple boilerplate for Webpack 3, dynamic imports, code splitting, and long term caching.",
  "main": "index.js",
  "scripts": {
    "start": "webpack --config webpack.config.js",
    "dev-server": "webpack-dev-server",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Geoff Miller",
  "license": "MIT",
  "dependencies": {
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "react-loadable": "^5.3.1",
    "react-router-dom": "^4.2.2"
  },
  "devDependencies": {
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-preset-env": "^1.6.1",
    "babel-preset-react": "^6.24.1",
    "babel-preset-stage-0": "^6.24.1",
    "html-webpack-plugin": "^2.30.1",
    "webpack": "^3.10.0",
    "webpack-dev-server": "^2.9.7"
  }
}

让我们通过这些配置文件,并揭秘发生了什么事情。

我总是从文件开始查看新的代码库的 package.json。这使我能够在代码中看到它们之前了解依赖关系。

js 复制代码
// package.json
"dependencies": {
    "react": "^16.2.0",
    "react-dom": "^16.2.0",
    "react-loadable": "^5.3.1", <-- dynamic imports for react
    "react-router-dom": "^4.2.2"
  },

在我们的应用程序中,react-loadable 将完成所有繁重的动态导入工作。这是一个小包装,减少样板和增加一些方便的功能。所以,而不是必须为每个动态导入执行此操作:

js 复制代码
class MyComponent extends React.Component {
  state = {
    Bar: null
  };

  componentWillMount() {
    import('./components/Bar').then((Bar) => {
      this.setState({ Bar });
    });
  }

  render() {
    let { Bar } = this.state;
    if (!Bar) {
      return <div>Loading...</div>;
    } else {
      return <Bar />;
    }
  }
}

我们现在可以做到:

js 复制代码
import Loadable from 'react-loadable';

const LoadableBar = Loadable({
  loader: () => import('./components/Bar'),
  loading() {
    return <div>Loading...</div>;
  }
});

class MyComponent extends React.Component {
  render() {
    return <LoadableBar />;
  }
}

好的,到 devDependencies:

js 复制代码
// package.json
"devDependencies": {
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-preset-env": "^1.6.1",
    "babel-preset-react": "^6.24.1",
    "babel-preset-stage-0": "^6.24.1", <-- needed for dynamic import
    "html-webpack-plugin": "^2.30.1",
    "webpack": "^3.10.0",
    "webpack-dev-server": "^2.9.7"
  }

这里唯一不同寻常的软件包是 babel-preset-stage-0。动态导入是第三阶段的建议。如果你想你可以使用 babel-preset-stage-3,但 babel-preset-stage-0 将包括所有的阶段 1,阶段 2 和阶段 3。根据需要将其配置为您的代码库/喜好,你需要的是动态导入功能的阶段 3。

js 复制代码
// .babelrc
{
  "presets": [
    "react",
    ["env", {
      "targets": {
        "browsers": ["last 2 versions"]
      }
    }],
    "stage-0" <-- no big surprise here
  ],
  "comments": true
}

确保包含 stage-0 在你的 babel 预设中。有些事情要注意,有几个动态的输入插件 webpack/babel 在那。在这个用例中你不需要使用 Webpack 3(前端只有 react)。如果我错了希望有人能纠正我。请注意”comments”: true 设置。这将帮助我们使用 Webpack 的神奇评论来命名我们的动态导入文件。

js 复制代码
// webpack.config.js
...
entry: {
    app: APP_DIR +'/index.js',
    vendor: Object.keys(package.dependencies)
},
output: {
    publicPath: '/',
    chunkFilename: '[name].[chunkhash].js',
    filename: '[name].[chunkhash].js'
  },
...
plugins: [
    ...
    new webpack.HashedModuleIdsPlugin(),
    new webpack.optimize.CommonsChunkPlugin({
      name:'vendor',
      filename: 'vendor.[chunkhash].js'
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name:'manifest'
    })
  ]
...

这就是“普通”React webpack 安装程序之外所需要的。这里有一些关于这个配置的更多细节。定义您的输入分割点。在我们的例子中,我们需要一个 app.x.js 和一个 vendor.x.js 文件:

js 复制代码
entry: {
  app: APP_DIR +'/index.js',
  vendor: Object.keys(package.dependencies)
},

[chunkhash]在输出中使用您的文件名 hash:

js 复制代码
output: {
  publicPath: '/',
  chunkFilename: '[name].[chunkhash].js',
  filename: '[name].[chunkhash].js'
},

在插件中,使用:

js 复制代码
new webpack.HashedModuleIdsPlugin();

那么你的 CommonsChunkPlugin 需要再次 chunkhash:

js 复制代码
new webpack.optimize.CommonsChunkPlugin({
  name:'vendor',
  filename: 'vendor.[chunkhash].js'
}),

最后,但一个容易忽略的细节:

js 复制代码
new webpack.optimize.CommonsChunkPlugin({
  name: 'manifest'
});

深入了解为什么清单文件在这里很重要。随意跳到 React 代码的下一节。没有 manifest chunk 块,Webpack 将产生这些文件:

js 复制代码
pageOne.58e60ef81ba97426a00d.js 679 bytes
   home.b65cb7fd922ea7126e95.js 671 bytes
    app.3c3fcb4d6bcba55f3bb7.js 3.44 kB
vendor.da901bd61614a0e9f2fe.js 1.25 MB
                     index.html 397 bytes

你可能会想:“太棒了,它正在工作!” 你去做你的事情,改变 home 组件,Webpack 建立你的文件,然后你会伤心的。

js 复制代码
  home.5a5f308381fc5670d102.js  674 bytes <--new hash expected
vendor.c509e728faa4374eee45.js  1.25 MB <--new hash not expected
                    index.html  397 bytes

是什么导致了?你没有给你的 package.json 改变/添加任何东西 。Webpack 文档简要地解释了这里发生了什么。这是非常高的水平,但我想知道什么是实际上改变的代码,以保证生成一个新的哈希 vendors.x.js 文件。

如果你看看里面的生成,vendor.x.js 你会看到:

js 复制代码
/******/ script.src =
  __webpack_require__.p +
  '' +
  ({ '0': 'pageOne', '1': 'home', '2': 'app' }[chunkId] || chunkId) +
  '.' +
  { '0': '58e60ef81ba97426a00d', '1': '5a5f308381fc5670d102', '2': '3c3fcb4d6bcba55f3bb7' }[chunkId] +
  '.js';

啊哈!这是所有我们的[chunkhash]价值观。因为我们改变了 home 组件,所以生成了一个新的散列,并将其添加到“webpack 样板文件”中,然后将其反转到我们的 vendor.x.js 文件中。不完全是我们想要发生的,看到我们的 vendor 文件不会经常更改,通常是我们构建的最大的文件。让我们用一个 webpack manifest 文件运行相同的练习。

js 复制代码
pageOne.58e60ef81ba97426a00d.js 679 bytes
    home.5a5f308381fc5670d102.js 674 bytes
  vendor.db2436c7653388db768a.js 1.24 MB
     app.8e527bb7a890edfc1ef3.js 3.44 kB
manifest.2b89533b2a3dea66348d.js 5.96 kB

改变 home

js 复制代码
    home.b65cb7fd922ea7126e95.js  671 bytes
manifest.c4a2e5b1ff1dcfbe35ae.js    5.96 kB

🎉 只有一个 home 组件和 manifest 产生新的散列 。让我们看看 manifest 文件里面:

js 复制代码
/******/ script.src =
  __webpack_require__.p +
  '' +
  ({ '0': 'pageOne', '1': 'home', '2': 'vendor', '3': 'app' }[chunkId] || chunkId) +
  '.' +
  {
    '0': '58e60ef81ba97426a00d',
    '1': 'b65cb7fd922ea7126e95',
    '2': 'db2436c7653388db768a',
    '3': '8e527bb7a890edfc1ef3'
  }[chunkId] +
  '.js';

现在我们在 manifest 中的[chunkhash]值正与我们的 vendor 文件混淆起来。在你的 vendor 文件中搜索这个代码,你会发现它已经消失了。感觉不错。最后是 React 代码。这可能是最酷的部分。我可以看到如此多的反应可加载的潜力。正常导入看起来像:

js 复制代码
import Home from './home';

所有我们必须做的,以获得正确的动态导入:

js 复制代码
import Loadable from 'react-loadable'
import Loading from './loading'
const Home = Loadable({
  loader: () => import('./home' /* webpackChunkName: 'home' */),
  loading: Loading, <-- a "loading" comp required by react-loadable
})

这里的所有都是它的。用 react-loadable 方式包装导入,并使用新的 import()语法。注意到 Webpack 中的评论/_ webpackChunkName: ‘yourcomponentname’ _/ 。无需指定 0.db2436c7653388db768a.js 的文件名。

评论

阅读上一篇

Commit message和Change log规范化
2017-12-16 00:48:34

阅读下一篇

优化 React 性能
2017-12-13 10:13:24
目录
0%