再學了前三節以後,其實咱們已經會對文件資源等進行一些打包,可是這些在大型項目中是遠遠不夠的,那麼咱們在平時的配置中還會遇到什麼難題呢?css
經過本節的學習,你能夠學會html
接上一講,若是咱們在preset-env設置了"useBuiltIns": "usage",那麼實際上咱們不去引入babel/polyfill也是能夠的。由於咱們在使用useBuiltIns,它會自動幫咱們引入,因此這節咱們直接能夠寫es6語法。vue
新建一個math.js,而後咱們在 m.js中引入,自行修改打包配置文件,若是你還不會請點擊3分鐘瞭解webapcknode
export const add = (a, b) => {
return a + b
}
export const minus = (a, b) => {
return a - b
}
// m.js
import { add } from './math'
console.log(add(1, 3))
複製代碼
這個時候咱們雖然實現了效果,可是在打包文件中,我卻將個人math文件徹底打包了。這裏我卻只引入了add方法,因此我是但願他只打包我引入的文件。因此在package.json中能夠作如下配置。jquery
"sideEffects": fasle
複製代碼
須要注意的是,這個在線上環境纔有用,由於他在開發中會方便咱們去調試。webpack
每次打包(線上,開發)代碼以前,咱們都會去不斷修改webpack.config.js中的文件,例如modo,插件等之類的,這樣的操做是很麻煩的,並且咱們也不可能100%保證咱們就不會改錯文件,畢竟改錯了文件的影響是很是大的,接下來咱們一塊兒看看若是區分線上與開發環境的配置。git
咱們以前有一個webpack.config.js,咱們將其重命名爲webpack.dev.js,而後複製一份改名webpack.prod.js。而後根據須要更新以下兩個文件。es6
// webpack.dev.js
const path = require('path'); // 從nodejs中引入path變量
const htmlPlugin = require('html-webpack-plugin'); // 引入html打包插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpack = require('webpack') // 引入webpack插件
module.exports = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
entry: {
main: './src/m.js',
},
devServer: {
contentBase: './dist', // 藉助webpack啓動服務器,根目錄就是打包以後的dist文件夾
open: true, // 啓動npm run start的時候自動打開瀏覽器
proxy: { // 配置代理
'/api': 'http://localhost:3000'
},
port: 8080, // 配置端口號
hot: true, // 開啓熱更新
//hotOnly: true // 就算是html文件沒生效也不刷新頁面
},
module: { // 模塊打包配置
// ... dev和prod同樣 不寫了
},
plugins: [
new htmlPlugin({
template: './index.html'
}),
new CleanWebpackPlugin(),
new webpack.HotModuleReplacementPlugin() // 引入插件
],
output: {
publicPath: '/',
filename: 'dist.js', // 打包後生成的main.js
path: path.resolve(__dirname, 'dist'), // 打包到dist文件夾
}
}
// webpack.prod.js
const path = require('path'); // 從nodejs中引入path變量
const htmlPlugin = require('html-webpack-plugin'); // 引入html打包插件
const cleanPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'production',
devtool: 'cheap-module-source-map',
entry: {
main: './src/m.js',
},
module: { // 模塊打包配置
// ...
},
plugins: [
new htmlPlugin({
template: './index.html'
}),
new cleanPlugin(['dist']),
],
output: {
publicPath: '/',
filename: 'dist.js', // 打包後生成的main.js
path: path.resolve(__dirname, 'dist'), // 打包到dist文件夾
}
}
複製代碼
ok,文件以及拆分了,這個時候咱們會修改package.json裏面的scriptsgithub
"scripts": {
"dev": "webpack-dev-server --config webpack.dev.js",
"build": "webpack --config webpack.prod.js"
},
複製代碼
重啓服務,打包文件正常運行。這樣咱們就區分開了,可是有一個很明顯的問題就是,這兩個文件,重複的地方太多了,若是我之後新增了一個公共的代碼,兩個文件都要加,刪除也是兩個文件都要作。這樣就會讓個人維護成本變高,並且仍是會增長錯誤的概率,因此咱們有必要對配置文件進行合併。web
先下載webapck-merge,他能夠幫助咱們合併webpack的配置
npm install webpack-merge -D
複製代碼
新建webpack.common.js進行代碼合併
// webpack.common.js
const path = require('path'); // 從nodejs中引入path變量
const htmlPlugin = require('html-webpack-plugin'); // 引入html打包插件
const cleanPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
main: './src/m.js',
},
module: { // 模塊打包配置
// ... 省略
},
plugins: [
new htmlPlugin({
template: './index.html'
}),
new cleanPlugin(['dist']),
],
output: {
publicPath: '/',
filename: 'dist.js', // 打包後生成的main.js
path: path.resolve(__dirname, 'dist'), // 打包到dist文件夾
}
}
// webpck.dev.js
const webpack = require('webpack') // 引入webpack插件
const webpackMerge = require('webpack-merge')
const commonConfig = require('./webpack.common')
const devConfig = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
devServer: {
contentBase: './dist', // 藉助webpack啓動服務器,根目錄就是打包以後的dist文件夾
open: true, // 啓動npm run start的時候自動打開瀏覽器
proxy: { // 配置代理
'/api': 'http://localhost:3000'
},
port: 8080, // 配置端口號
hot: true, // 開啓熱更新
//hotOnly: true // 就算是html文件沒生效也不刷新頁面
},
plugins: [
new webpack.HotModuleReplacementPlugin() // 引入插件
]
}
module.exports = webpackMerge(commonConfig, devConfig)
// webpack.prod.js
const webapckMerge = require('webpack-merge')
const commonConfig = require('./webpack.common')
const prodConfig = {
mode: 'production',
devtool: 'cheap-module-source-map',
}
module.exports = webapckMerge(commonConfig, prodConfig)
複製代碼
npm run dev,ok nice.
代碼分割,這個我就舉例說明一下就ok。
在咱們平時使用vue等大框架的時候,常常會用到一個lodash.js,假設咱們正常的下載並使用改代碼。
文件 | 大小 |
---|---|
lodash.js | 1MB |
axin.js | 1MB |
// axin.js
import _ form 'lodash'
// 使用lodash
複製代碼
這樣的話,假設咱們的代碼不作壓縮,咱們的代碼就會達到2MB大小,若是用戶打開咱們的網頁,這個時候咱們就會先去加載這個2mb的文件,這樣的話,對用戶體驗很很差。那若是咱們可以達到以下效果,就好多了 。
// lo.js
import _ form 'lodash'
window._ = _
// axin.js
// 使用lodash.js
複製代碼
js是支持並行加載的,不能說必定比2m的快,可是至少能優化很多,最大的好處是什麼?是咱們若是隻是修改了axin.js的內容,那咱們的lo.js是不須要改變的,瀏覽器中會有緩存,這個時候想要的效果就會明顯提高。
那麼咱們在webpack中應該如何配置呢?找到webpack.common.js
optimization: {
splitChunks: {
chunks: 'all'
}
},
複製代碼
這個時候就會幫咱們去拆分代碼,須要特別說明的是,webpack和Code Splitting是沒有關係的,默認的會幫咱們下載一個功能,咱們只須要配置便可。
這個是同步加載的方式,有時候咱們的文件是異步回來的,其實也是這麼一回事。我就很少作演示。 你們有興趣的能夠本身下來試試。
爲了搞清楚,這個插件,仍是沒能逃避寫一個異步加載的方法來使用組件。
function getComponent(){
return import('lodash').then(({ default: _}) => {
var element = document.createElement('div')
element.innerHTML = _.join(['jsxin', 'hello'], '-')
return element
})
}
getComponent().then(element => {
document.body.appendChild(element)
})
複製代碼
// {default: } 加載回來的賦值給_
不管是同步加載或者異步,咱們都會進行代碼分割。咱們先來下載一個官方提供的動態引入的插件。
平常直通車:babeljs.io/docs/en/nex…
npm install --save-dev @babel/plugin-syntax-dynamic-import
// .babelrc
{
"plugins": ["@babel/plugin-syntax-dynamic-import"]
}
//package.json
"dev-build": "webpack --config webpack.dev.js",
複製代碼
webpack-dev-server會把文件寫到內存咱們是觀察不到的,因此新增一個命令npm run dev-build,讓其打包代碼。
這時候給我生成了一個0.dist.js
咱們能夠在引入以前使用註釋符爲其設置名字
function getComponent(){
return import(/* webpackChunkName: "loadash" */'lodash').then(({ default: _}) => {
var element = document.createElement('div')
element.innerHTML = _.join(['jsxin', 'hello'], '-')
return element
})
}
複製代碼
就會生成一個vendors~lodash.dist.js
由於這裏設置的比較多,咱們簡單的把配置項講解一下,如下爲配置項,若是你的splitChunks沒有配置任何內容,就會使用如下的內容做爲配置項。
optimization: {
splitChunks: {
chunks: 'async', // all 不區分 async 只對異步代碼生效
minSize: 30000, // 打包最小30000字節我纔去分割
minRemainingSize: 0,
maxSize: 0, // 通常配置 50000 就至關於能拆分紅幾個50kb左右的
minChunks: 1, // 最少使用一次
maxAsyncRequests: 6, // 同時加載的模塊數最多6個
maxInitialRequests: 4, // 入口文件也會拆分 可是最多4個 超過了就不分分割了
automaticNameDelimiter: '~', // 名字和組的拼接符 vendors~lodash
cacheGroups: { // 拆分分組
defaultVendors: { // 默認分組
test: /[\\/]node_modules[\\/]/, // 若是是node_modules中的咱們就到defaultVendors這個組
priority: -10, // 優先級, 和下面default 同時知足條件 打包到優先級高的裏面
// filename: 'vendor.js' 能夠本身取名字
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true // 好比以前引用了a代碼,就不會打包a到common.js,會複用
// filename: 'common.js' 能夠本身取名字
}
}
}
},
複製代碼
咱們對剛纔的異步代碼作一點改進
function getComponent(){
return import(/* webpackChunkName: "loadsh" */'lodash').then(({ default: _}) => {
var element = document.createElement('div')
element.innerHTML = _.join(['jsxin', 'hello'], '-')
return element
})
}
document.addEventListener('click', () => {
getComponent().then(element => {
document.body.appendChild(element)
})
})
複製代碼
咱們仍是異步加載一個loadsh函數,而後在頁面中綁定了一個點擊事件,只有咱們監聽到點擊事件的時候,咱們纔回去調用getComponent方法,而後經過getComponent方法去引入loadsh函數。
效果就是咱們在頁面中,開始只會加載一個main.js,而後點擊一下頁面會在加載一個loadsh函數,調用這個函數的某些方法咱們實現了一個字符串的拼接過程,最終呈如今了頁面上。
經過import方法,咱們只有訪問了在某些文件的時候,他纔會異步加載,而後執行。這樣咱們加載速度也會更快。
固然後也可使用es7中比較流行的async來處理這個時間,讓大家的代碼更加直爽。
async function getComponent() {
const { default: _ } = await import(/* webpackChunkName: "loadsh" */'lodash')
const element = document.createElement('div')
element.innerHTML = _.join(['jsxin', 'hello'], '-')
return element
}
複製代碼
咱們在以前已經使用了不少次chunk了,那麼咱們這個chunk究竟是什麼?
在js代碼打包中,咱們會拆分紅多個js文件,那麼每個js文件,咱們都稱它爲一個chunk。
先來看看官方的webpack分析工具
若是你相對咱們打包以後的代碼進行分析,首先你須要將--profile --json > stats.json 放到你打包的命令中
"dev-build": "webpack --profile --json > stats.json --config webpack.dev.js",
複製代碼
他的意思就是將個人打包過程放到stats.json這個文件中。
他會將咱們整個打包的流程都寫進入,比較耗時,打包了什麼資源,有幾個模塊,幾個chunk等,你能夠能夠藉助官方工具幫你翻譯一下。這裏你們能夠了解一下,我就很少作介紹,能夠自行打包嘗試。
在這個知識點以前咱們先來看看咱們最原始的代碼寫法。
document.addEventListener('click', () => {
const element = document.createElement('div')
element.innerHTML = 'jsxin'
document.body.appendChild(element)
})
複製代碼
這個是咱們經常使用的標準寫法,難道這個寫法就沒有優化空間了嗎?
編譯成功以後咱們打開f12, 而後按住command+shift+p,輸入coverage這個關鍵詞
咱們點擊一下show Coverage,而後左側會出現一個錄製按鈕,咱們會發現咱們的main.js只有75%的代碼使用率。
爲何呢?由於咱們在頁面加載的使用,咱們並不會使用
const element = document.createElement('div')
element.innerHTML = 'jsxin'
document.body.appendChild(element)
複製代碼
這些代碼是在被點擊的時候纔會用到,因此這不是webpack推薦的一種書寫代碼的方式。
咱們能夠將這部分代碼這麼寫,新建click.js
// click.js
export default function addComponent () {
const element = document.createElement('div')
element.innerHTML = 'jsxin'
document.body.appendChild(element)
}
// com.js
document.addEventListener('click', () => {
import('./click.js').then(({ default: _ }) => {
_()
})
})
複製代碼
這樣的話,他的使用了就達到了79%,也會節約咱們的首屏加載時間。
咱們一個網頁,剛開始初始化首頁的時候,咱們不加載登陸模態框,先加載首頁的其餘邏輯,等加載完成以後,帶寬被釋放出來了,咱們偷偷的加載登陸模態框,這樣的話,既知足了我首頁加載快的需求,又知足了登陸加載快的需求。
而這個方案就是咱們結合prefetching和preloading的一個比較實用的例子。
能夠在import以前聲明prefetching配置
import(/* webpackPrefetch: true */'./click.js')
複製代碼
這個時候,等他將咱們的核心代碼加載完成以後,就會偷偷的加載click.js
咱們生成以下代碼
import '../statics/style/index.css'
console.log(123)
複製代碼
我如今但願個人index.css不直接生成css代碼到個人頁面上,而是但願他在dist下面新建一個文件夾,而後把css放進去引入,那麼這麼時候咱們應該怎麼處理這種操做呢?
官方插件:webpack.js.org/plugins/min…
特別說明:適合線上環境中使用,由於更新以後不會自動刷新
先來安裝一下插件
npm install --save-dev mini-css-extract-plugin
複製代碼
而後在線上環境中使用。
// webpack.prod.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
plugins: [new MiniCssExtractPlugin()],
複製代碼
而後咱們以前使用的style-loader就不能用了,他給咱們提供了一個loader,咱們將style-loader替換成他的loader,而後還要將css區分開線上與開發環境。
// webpack.prod.js
module: {
rules: [{
test: /\.css$/, // 檢測文件是css結尾的
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.scss$/, // 檢測文件是scss結尾的
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 2, // 經過import引入的scss文件,也要走下面兩個loader
// modules: true
}
},
'sass-loader',
'postcss-loader'
]
}]
}
複製代碼
而後咱們打包一個線上的代碼試試。就在咱們的代碼中生成了main.css文件。
咱們簡單的看看他的配置項
plugins: [new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[name].chunk.css'
})],
複製代碼
若是樣式是直接被引用,他就會走filename,間接就是chunkfilename
咱們不妨再來作一些嘗試。
// index.css
.avatar{
width: 100px;
height: 100px;
}
// index1.css
.avatar{
display: block;
}
// style.css
import '../statics/style/index.css'
import '../statics/style/index1.css'
console.log(123)
複製代碼
咱們再次打包,你會發現,他自動將兩個樣式文件給我合併到main.css中了。
// 打包後
.avatar{
width: 100px;
height: 100px;
}
.avatar{
display: block;
}
/*# sourceMappingURL=main.css.map*/
複製代碼
ok,那麼咱們若是還想對這個css進行一些壓縮怎麼辦呢?
// install
npm install optimize-css-assets-webpack-plugin -D
// prod
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
optimization: {
minimizer: [new OptimizeCSSAssetsPlugin({})]
}
複製代碼
而後在打包,他不只將咱們的代碼進行了壓縮,還將咱們的代碼合併到了一塊兒。
// 打包後
.avatar{width:100px;height:100px;display:block}
複製代碼
ok,其餘更高級的用法,請參考官方文檔,這裏一方面是和你們一塊兒體驗,另外一方面是推薦經常使用的插件。
咱們在加載一個網頁的時候,咱們可能首先會加載一個index.html,和兩個js文件,當你下次訪問的時候,其實瀏覽器已經對你兩個js有緩存了,這個時候會優先讀取緩存中的文件。
這個時候要麼你更改一下文件的名字,要麼就強制刷新,可是你確定不能讓用戶強制刷新頁面。因此咱們在調試過程當中能夠無論,從新配置一下output
// cache.sj
import _ from 'lodash'
let str = _.join(['j', 's', 'x', 'i', 'n'], '-')
console.log(str)
output: {
filename: '[name].[contenthash].js',
}
複製代碼
contenthash就是咱們根據內容生成的一個hash值,只要你的內容沒有改變,那麼咱們就不用從新去加載這些js。
咱們改變的是什麼?咱們會修改本身的邏輯源代碼,可是你並不會去改變node_modules的第三方代碼,因此這些東西確定仍是可讓瀏覽器讀緩存,提升網站加載效率。
webpack是基於模塊打包的,也就是說咱們在一個模塊裏面的代碼,到另一個模塊就找不到了。
// jq.js
import $ from 'jquery'
import { jqui } from './jq.ui'
jqui()
$('body').append('<div>axin</div>')
// jq.ui.js
export function jqui(){
$('body').css('background','red')
}
// $ is not defined
複製代碼
webpack是提供了一下插件的,咱們來看看他是幹嗎的。
new webpack.ProvidePlugin({
$: 'jquery'
})
複製代碼
他會去檢測你哪一個文件是使用了$符,若是使用了,那麼你是否在上面引入了jquery,若是沒有的話,他就會自動幫你在上面引入,很是nice。
還記得咱們使用了一個babel/polyfill嗎?若是你沒有promise等,他會幫你完成promise的實現。
咱們以前是在dev/prod環境中合併的common,那麼這裏咱們來使用一個環境變量來從新合併咱們的打包配置文件,先上代碼。
// dev/prod 註釋如下代碼 而後分包導出
const webapckMerge = require('webpack-merge')
const commonConfig = require('./webpack.common')
module.exports = prodConfig
module.exports = devConfig
// webpack.common.js
const webapckMerge = require('webpack-merge')
const prodConfig = require('./webpack.prod')
const devConfig = require('./webpack.dev')
const commonConfig = ...(配置)
module.exports = (env) => {
if(env && env.production){
return webapckMerge(prodConfig, commonConfig)
}
return webapckMerge(devConfig, commonConfig)
}
複製代碼
這裏咱們直接導出了一個函數,接收了一個env,因此咱們在打包腳本中這麼這麼寫
"scripts": {
"dev-build": "webpack --profile --json > stats.json --config webpack.common.js",
"dev": "webpack-dev-server --config webpack.common.js",
"build": "webpack --env.production --config webpack.common.js"
},
複製代碼
配置了env變量,咱們在打包的時候都是webpack.common.js,而後判斷就能夠不一樣的配置進行打包了。
ok,就到這裏吧,知識點即總結,若是你也想和我一塊兒學習webpack,我們第五節見。