從 Webpack 1 到 Webpack 3!
因為 海總理 日日逼迫嗚嗚,而且 StreetVoice 原本的 js files 太巨大,再加上官方已經停止維護 Webpack 1:
webpack v1 is deprecated. We encourage all developers to upgrade to webpack 2.
所以就來升級啦!
至於可能有人想問為什麼直接從 v1 升到 v3?
完全是因為動作太慢哈哈哈!要升級時 Webpack 版本號已經來到 3.5 了,那乾脆就跳過 v2。
這次升級後可以明顯感受到的差異是:
- build 的速度快了 33 %!
- webpack config 的寫法比之前好理解一些
- 終於有乖乖做 dynamic import
以下會將這次升級的過程分成幾個部分來說明!
升級相關的調整
升級相關套件:
$ npm install —save-dev webpack@^3.5.6 webpack-bundle-tracker webpack-dev-middleware babel babel-core babel-loader babel-polyfill babel-preset-es2015 babel-preset-react uglifyjs-webpack-plugin lodash-webpack-plugin$ npm install —save react@^15.6.0 react-dom@^15.6.0 prop-types react-addons-update react-transition-group
修改 webpack.config.js:
- resolve.root, resolve.modulesDirectories 的內容統一放到 resolve.modules
- resolve.extensions 陣列中第一個不用再放空字串
- module.loaders.loaders 改為 module.rules.use
- 開發時使用的 NoErrorsPlugin 更名為 NoEmitOnErrorsPlugin
- Production 使用 ModuleConcatenationPlugin 減少 closure 數量加快 JS 執行速度
resolve: {
modules: [path.resolve('./static/jsx'), 'node_modules'],
extensions: ['.js', '.jsx'],
},
module: {
rules: [
{
...,
use: ['babel-loader'],
},
]
},
plugins: [
new webpack.optimize.ModuleConcatenationPlugin(),
new webpack.NoEmitOnErrorsPlugin(),
]
.babelrc
{
"presets": [
["es2015", { "loose" : true, "modules": false }],
"react",
],
"plugins": [
"react-hot-loader/babel", // webpack hmr
"transform-object-rest-spread", // webpack 3 import()
"transform-class-properties", // ES6 class
"syntax-dynamic-import", // ES6 decorator
"transform-decorators-legacy", // ES6 spread properties
],
}
既然是升級,那 react 也要一起!
基本上升級 react 的過程中沒遇到什麼問題,唯一一個是 this.refs.xxx
正式被棄用,不再支持字串的用法,使用 callback 的方式更有彈性。
// before
<MyComponent ref="foo" />// usage
this.refs.foo// after
<MyComponent ref={(el) => { this.foo = el; }} />// usage
this.foo
寫了個 HOC 處理 dynamic import
在這次升級之前其實都沒有好好實施 dynamic import(打屁股),所以就來使用 Webpack 的 import()
吧!
順便提一下 Webpack 3 的新功能 — Magic comment: 可以使用 import( /* webpackChunkName: "chunk-name" */ "module")
來替 chunk 命名。
// LazyLoading.jsx
import React from 'react';const LazyLoading = (getComponent, moduleName = 'default') => class LazyLoadingWrapper extends React.Component {
constructor(props) {
super(props);this.state = {
Component: null,
};
}componentWillMount() {
getComponent()
.then((esModule) => {
// store the component in state
this.setState({ Component: esModule[moduleName] });
})
.catch((err) => {
console.log('error')
console.log(err)
});
}render() {
const { Component } = this.state;// react cannot render null component
if (!Component) return <div style={{ display: 'none' }}></div>;return <Component {...this.props} />;
}
};export default LazyLoading;// webpack.config.js
output: {
...,
chunkFilename: 'chunk-[name]-[chunkhash].js',
}// Usage
import LazyLoading from 'LazyLoading.jsx';LazyLoading(() => import(/* webpackChunkName: "MyLazyLoagingComponent" */ 'path-to-component'));
發文附圖!
上圖以 StreetVoice 加入歌單的功能為例,使用 dynamic import 之後,就會動態載入相應的 chunk ,減少網站第一次載入的檔案大小,進而降低網站載入的時間。
使用 dynamic import 後在 production 拿不到 chunk files
因為當初的 webpack config 並沒有特別指定 publicPath
,導致在動態載入時跑去 /
拿了,所以請大家 千萬 不要忘了在 webpack.config.js 加上 publicPath
!
// webpack.config.js
output: {
publicPath: 'your-CDN',
}
從 webpack-dev-server 到 webpack-hot-middleware
同時也升級了 react-hot-loader ,跟著官方的 Upgrade Guide ,升完看起來不錯?但實際測試後發現了幾個問題:
- component 外面多了一層
<AppContainer>
導致原本 component 的 public method 無法順利被呼叫 - 在 build production 時 react-hot-loader 相關的 code 也被 bundle 進去導致檔案變大
- 有些 component hmr 會失效
一氣之下改用 express + webpack-dev-middleware + webpack-hot-middleware 試試看,結果意外解決了問題?
想不通為什麼換了一套之後 hmr 就沒有問題了,如果有人知道可以留言告訴我嗎?
麻煩勒 (o´・ε・`o)
// server.js
const express = require('express');
const webpack = require('webpack');
const config = require('path-to-config');
const createWebpackMiddleware = require('webpack-dev-middleware');
const createWebpackHotMiddleware = require('webpack-hot-middleware');
const path = require('path');const compiler = webpack(config);
const app = express();const webpackDevMiddleware = createWebpackMiddleware(compiler, {
publicPath: config.output.publicPath,
hot: true,
headers: {'Access-Control-Allow-Origin': '*'},
inline: true,
historyApiFallback: true,
watchOptions: {
poll: 500
}
});app.use(webpackDevMiddleware);
app.use(createWebpackHotMiddleware(compiler));
app.listen(3000, () => console.log('Listening at http://localhost:3000'));// webpack.config.js
entry: {
'app': [
'babel-polyfill',
'react-hot-loader/patch',
'webpack/hot/only-dev-server',
'webpack-hot-middleware/client?path=http://localhost:3000/__webpack_hmr',
'./src/app',
]
}// App.js
function render() {
ReactDOM.render(
<App />,
document.getElementById('root')
);
}render();if (module.hot) {
module.hot.accept('path-to-App', () => {
render();process.env.NODE_ENV
}
}
而檔案變大則是因為 babel plugin 在 build 時 process.env.NOD_ENV
還是 undefined,在 build 時加上 NODE_ENV=production
就解決了。
// package.json
"scripts": {
"build-production": "NODE_ENV=production webpack --config webpack.config.js --progress",
}
這樣就完成升級啦!希望可以幫助升級苦手的人:)
工商時間
歡迎喜歡獨立音樂的工程師一起來 StreetVoice 玩,當然只要愛寫扣都歡迎啦哈哈哈,我們缺 Python 工程師!也缺 PM ,什麼都缺,歡迎洽詢!