因爲 Parcel 打包工具的影響,webpack4 也追求零配置搭建項目。而前陣子出現的 vue-cli 3.0也是基於 webpack4 零配置的思想建立的。對於一些習慣webpack3 的開發者不免有些不習慣。本文就帶你繞過 vue-cli,用 webpack4 一步步搭建 vue 項目。javascript
注:(本文講述的是webpack4基礎配置,文章有點長,請耐心看完。或者直接查看項目源碼,或者ctrl + w
)css
建立 createVue 文件夾,進入該文件夾, npm init
初始化項目html
安裝 webpack 四件套vue
npm i webpack webpack-cli webpack-dev-server webpack-merge --save-dev
java
// 當前我使用版本
"webpack": "^4.16.3",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.5", // 開發服務器
"webpack-merge": "^4.1.4" // webpack 配置合併
複製代碼
createVue
|--dist
|--build
|--webpack.prod.js
|--webpack.dev.js
|--webpack.base.js
|--src
|--index.js
|--app.vue
|--index.html
複製代碼
// webpack.base.js
// 存放 dev 和 prod 通用配置
const webpack = require('webpack');
const path = require("path");
module.exports = {
entry: './src/index.js', //入口
module: {
rules: []
},
plugins: [
// 解決vender後面的hash每次都改變
new webpack.HashedModuleIdsPlugin(),
],// 插件
};
複製代碼
// webpack.dev.js
// 存放 dev 配置
const merge = require('webpack-merge');
const common = require('./webpack.base.js');
const path = require('path');
module.exports = merge(common, {
devtool: 'inline-source-map',
devServer: { // 開發服務器
contentBase: '../dist'
},
output: { // 輸出
filename: 'js/[name].[hash].js', // 每次保存 hash 都變化
path: path.resolve(__dirname, '../dist')
},
module: {},
mode: 'development',
});
複製代碼
// webpack.prod.js
// 存放 prod 配置
const path = require('path');
// 合併配置文件
const merge = require('webpack-merge');
const common = require('./webpack.base.js');
module.exports = merge(common, {
module: {},
plugins: [],
mode: 'production',
output: {
filename: 'js/[name].[contenthash].js', //contenthash 若文件內容無變化,則contenthash 名稱不變
path: path.resolve(__dirname, '../dist')
},
});
複製代碼
webpack4 增長了 mode 屬性,設置爲 development / production,如下是默認配置node
development:
process.env.NODE_ENV 的值設爲 development
默認開啓如下插件,充分利用了持久化緩存。參考基於 webpack 的持久化緩存方案
NamedChunksPlugin :以名稱固化 chunk id
NamedModulesPlugin :以名稱固化 module id
production:
process.env.NODE_ENV 的值設爲 production
默認開啓如下插件,其中 SideEffectsFlagPlugin 和 UglifyJsPlugin 用於 tree-shaking
FlagDependencyUsagePlugin :編譯時標記依賴
FlagIncludedChunksPlugin :標記子chunks,防子chunks屢次加載
ModuleConcatenationPlugin :做用域提高(scope hosting),預編譯功能,提高或者預編譯全部模塊到一個閉包中,提高代碼在瀏覽器中的執行速度
NoEmitOnErrorsPlugin :在輸出階段時,遇到編譯錯誤跳過
OccurrenceOrderPlugin :給常常使用的ids更短的值
SideEffectsFlagPlugin :識別 package.json 或者 module.rules 的 sideEffects 標誌(純的 ES2015 模塊),安全地刪除未用到的 export 導出
UglifyJsPlugin :刪除未引用代碼,並壓縮
複製代碼
// index.js
// 需 npm i vue --save
import Vue from 'vue';
import App from './App.vue'
import './index.scss'
new Vue({
el: '#app',
render: h => h(App),
});
複製代碼
<!-- app.vue -->
<template>
<div id="app">
hello world
</div>
</template>
<script> export default { name: 'app' } </script>
<style scoped> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; transform: rotate(0deg); } </style>
複製代碼
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Suporka Vue App</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
複製代碼
npm i vue-loader vue-template-compiler --save-dev
webpack
// 當前我使用版本
"vue-loader": "^15.2.6",
"vue-template-compiler": "^2.5.17",
複製代碼
因爲 vue 的解析在 dev 和 prod 中均需使用,所以納入基本配置 basegit
// webpack.base.js
// ...省略號
// vue-loader 插件
const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports = {
//...省略號
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
}
]
},
plugins: [
// 請確保引入這個插件來施展魔法
new VueLoaderPlugin(),
]
};
複製代碼
npm i html-webpack-plugin --save-dev
github
// 當前版本
"html-webpack-plugin": "^3.2.0"
複製代碼
html 解析也屬於基本配置,納入 baseweb
// webpack.base.js
// ...省略號
// html插件
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
//...省略號
plugins: [
//...省略號
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../index.html'),
}),
]
};
複製代碼
"scripts": {
"start": "webpack-dev-server --hot --open --config build/webpack.dev.js",
"build": "webpack --config build/webpack.prod.js"
},
複製代碼
--hot 模塊熱替換
--open 開啓本地服務器
此時 npm start
,項目可正常運行
CSS 基礎 loader
"css-loader": "^1.0.0",
"style-loader": "^0.21.0",
複製代碼
CSS 前處理 less 兩件套
"less": "^3.8.0",
"less-loader": "^4.1.0",
複製代碼
CSS 前處理 sass 兩件套
"node-sass": "^4.9.2",
"sass-loader": "^7.1.0",
複製代碼
CSS 後處理 postcss 兩件套
"postcss-loader": "^2.1.6",
"autoprefixer": "^9.1.0",
複製代碼
並在根文件夾建立 postcss.config.js 文件
// postcss.config.js
// 自動添加css兼容屬性
module.exports = {
plugins: [
require('autoprefixer')
]
}
複製代碼
安裝以上依賴,在 base 文件中加入一下 loader 代碼
// webpack.base.js
// ...省略號
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'sass-loader',
],
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'postcss-loader',
'less-loader',
],
},
]
複製代碼
解析圖片,字體等都是用 file-loader,安裝npm i file-loader --save-dev
base 文件加入配置
// webpack.base.js
// ...省略號
rules: [
// ...省略號
{
test: /\.(png|svg|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
limit: 5000,
// 分離圖片至imgs文件夾
name: "imgs/[name].[ext]",
}
},
]
},
]
複製代碼
// webpack.prod.js
// 打包以前清除文件
const CleanWebpackPlugin = require('clean-webpack-plugin');
// ...省略號
plugins: [
new CleanWebpackPlugin(['dist/*'], {
root: path.resolve(__dirname, '../')
}),
]
複製代碼
webpack4 中使用 mini-css-extract-plugin 插件來分離 css。
// webpack.prod.js
// 分離CSS插件
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// ...省略號
plugins: [
new MiniCssExtractPlugin({
filename: "css/[name].[hash].css",
chunkFilename: 'css/[id].[hash].css'
}),
]
複製代碼
另外,還需將各個 css loader中的style-loader 替換爲 MiniCssExtractPlugin
圖片壓縮使用 image-webpack-loader
, 安裝後 代碼以下:
// webpack.prod.js
// ...省略號
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
// you can specify a publicPath here
// by default it use publicPath in webpackOptions.output
publicPath: '../'
}
},
'css-loader',
'postcss-loader',
'sass-loader',
],
},
{
test: /\.less$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
// you can specify a publicPath here
// by default it use publicPath in webpackOptions.output
publicPath: '../'
}
},
'css-loader',
'postcss-loader',
'less-loader',
],
},
{
test: /\.(png|svg|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
limit: 5000,
name: "imgs/[hash].[ext]",
}
},
// 圖片壓縮
{
loader: 'image-webpack-loader',
options: {
// bypassOnDebug: true,
mozjpeg: {
progressive: true,
quality: 65
},
optipng: {
enabled: false,
},
pngquant: {
quality: '65-90',
speed: 4
},
gifsicle: {
interlaced: false,
}
},
},
]
},
]
複製代碼
同時也須要安裝 babel 兩件套
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"happypack": "^5.0.0",
複製代碼
happypack 開發生產環境都用到,配置納入 base
// webpack.base.js
// 使用happypack
const HappyPack = require('happypack');
const os = require('os');
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
// ...省略號
rules: [
{
test: /\.js$/,
//把對.js 的文件處理交給id爲happyBabel 的HappyPack 的實例執行
loader: 'happypack/loader?id=happyBabel',
//排除node_modules 目錄下的文件
exclude: /node_modules/
},
]
plugins: [
//...
new HappyPack({
//用id來標識 happypack處理類文件
id: "happyBabel",
//如何處理 用法和loader 的配置同樣
loaders: [
{
loader: "babel-loader?cacheDirectory=true"
}
],
//共享進程池
threadPool: happyThreadPool,
//容許 HappyPack 輸出日誌
verbose: true
}),
]
複製代碼
// webpack.prod.js
module.exports = merge(common, {
// ...省略號
optimization: {
// 分離chunks
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
name: "vendor",
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: "initial" // 只打包初始時依賴的第三方
},
}
},
},
})
複製代碼
如此配置,則打包的 js 文件夾中會多一個 vendor.js
安裝 optimize-css-assets-webpack-plugin 和 uglifyjs-webpack-plugin 插件
// webpack.prod.js
// 壓縮CSS和JS代碼
// ...省略號
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
module.exports = merge(common, {
// ...省略號
optimization: {
// ...省略號
minimizer: [
// 壓縮JS
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false, // 去除警告
drop_debugger: true, // 去除debugger
drop_console: true // 去除console.log
},
},
cache: true, // 開啓緩存
parallel: true, // 平行壓縮
sourceMap: false // set to true if you want JS source maps
}),
// 壓縮css
new OptimizeCSSAssetsPlugin({})
]
},
})
複製代碼
最後,再拓展一個 hash, chunkhash, contenthash 的區別
hash是跟整個項目的構建相關,只要項目裏有文件更改,整個項目構建的hash值都會更改,而且所有文件都共用相同的hash值
chunkhash和hash不同,它根據不一樣的入口文件(Entry)進行依賴文件解析、構建對應的chunk,生成對應的哈希值。
contenthash 更細緻地根據內容更改,生成對應的哈希值。解決chunkhash 文件中引入的文件名因 chunkhash 變更而變更的問題