1 前言
這篇文章主要分享使用webpack作為構建工具的SPA項目的代碼拆分經驗,需要一些基本的webpack知識。
文章內容主要來自于個人開發中的總結以及網上的學習資料,有說的不對或者不準確的地方,歡迎指正。
2 什么是代碼拆分
代碼拆分是指我們把所有的JS代碼拆分為多個JS文件,需要某塊代碼時再去加載,以加快頁面的加載速度.
代碼拆分在我看來有三個類型:
- 庫代碼和業務代碼拆分
- 不同路由的代碼需要拆分
- 一些特殊操作后才需要顯示的內容對應的代碼需要拆分
以下會分別講解
3 分離庫代碼和業務代碼
在開發過程中我們不可避免會使用第三方庫,比如JQuery、Vue、React等等,這部分庫一般不會發生變化(除非版本升級)。對于這些第三方庫的代碼,一般有兩種處理方式
- 統一打包到一個chunk中
- 使用cdn加載
下面分別講解
3.1 把第三方庫打包到統一的chunk
代碼如下,以下代碼表示把來自node_modules目錄的js文件打包到一起,并命名為lib
new webpack.optimize.CommonsChunkPlugin({
name: 'lib',
minChunks: ({ resource }) => (
resource &&
resource.indexOf('node_modules') >= 0 &&
resource.match(/\.js$/)
)
}),
3.2 使用cdn加載第三方庫
需要使用webpack的externals屬性,配置示例如下:
externals: {
'react': 'React',
'react-dom': 'ReactDOM',
'react-router': 'ReactRouter',
...
},
然后在html中需要手動加入script標簽來引入,也可以自己寫webpack插件來自動生成。我自己寫的插件代碼如下
function InsertScript(paths) {
this.paths = paths || []
}
InsertScript.prototype.apply = function (compiler) {
var paths = this.paths
compiler.plugin('compilation', function (compilation, options) {
compilation.plugin('html-webpack-plugin-before-html-processing', function (htmlPluginData, callback) {
paths.reverse().forEach(function(path) {
htmlPluginData.assets.js.unshift(path)
})
callback(null, htmlPluginData)
})
})
}
module.exports = InsertScript
3.3 混合方案
我們引用的第三方庫既有react這種穩定的庫,也可能引入antd、lodash這種可以按需加載的庫。個人覺得對于react這種庫適合cdn引入;而antd這種庫因為按需加載會造成經常變化,所以適合單獨打包。
4 根據路由進行代碼拆分(以react為例)
以下是使用對象方式配置react-router的簡單例子
import Home from 'path-to-home'
import About form 'path-to-about'
const routes = [
{ path: '/home', component: Home },
{ path: '/about', component: About }
]
4.1 使用require.ensure
針對webpack 1.X版本。進行修改如下
import Home from 'path-to-home'
const About = (location, cb) => {
require.ensure([], require => {
cb(null, require('path-to-about').default)
}, 'about')
}
const routes = [
{ path: '/home', component: Home },
{ path: '/about', getComponent: About }
]
4.2 使用動態import
針對webpack 2.4版本以后,利用import返回的是promise
先編寫lazyLoad.js, 如下
// lazyload.js
import React from 'react'
const Loading = () => (
<div>
頁面加載中....
</div>
)
const Err = () => (
<div>
頁面加載失敗
</div>
)
const lazyLoad = (getComponent) => {
class LazyLoadWrapper extends React.Component {
state = { Component: undefined }
componentWillMount () {
getComponent()
.then((esModule) => { this.setState({ Component: esModule.default }) })
.catch(() => { this.setState({ Component: Err }) })
}
render () {
const { Component = Loading } = this.state
return <Component {...this.props} />
}
}
return LazyLoadWrapper
}
export default lazyLoad
路由配置修改如下
import lazyLoad from 'path-to-lazyload'
import Home from 'path-to-home'
const About = lazyLoad(() => (
import(/* webpackChunkName: "about" */ 'path-to-about')
))
const routes = [
{ path: '/home', component: Home },
{ path: '/about', component: About }
]