最近學(xué)習(xí)webpack相關(guān)知識,特此記錄下學(xué)習(xí)過的文檔以及搭建過程氧卧;如有錯誤该镣,記得告訴我呀廓译。項目地址:地址,求星星
// 1埃仪、clone代碼到本地
cd vue-demo
npm i
// 通過package.json的scripts可以看到區(qū)分了不同環(huán)境的啟動命令
npm run dev:local // 例如:啟動
npm run build // 打包
[TOC]
vue項目搭建
傳送門:相關(guān)代碼都在這里哦卵蛉!
tips: 當(dāng)前項目搭建時環(huán)境及使用的部分工具版本(版本不同可能導(dǎo)致使用方法不同):node v11.6.0, npm v6.10.0, webpack: ^4.35.0, webpack-cli: ^3.3.5, 其他請看package.json
初始化項目
1.新建vue-demo, cd vue-demo, npm init初始化項目颁股;
2.安裝相關(guān)依賴:
webpack: npm i webpack webpack-cli webpack-dev-server webpack-merge --save-dev
vue: npm i vue --save
、npm i vue-loader vue-template-compiler --save-dev
html解析:npm i html-webpack-plugin --save-dev
css毙玻、scss相關(guān):npm i css-loader style-loader node-sass sass-loader --save-dev
css后處理:npm i postcss-loader autoprefixer --save-dev
圖片路徑處理:npm i file-loader url-loader --save-dev
以下為打包時用到的插件豌蟋,放在webpack.prod.js:
清理dist文件夾:npm i clean-webpack-plugin --save-dev
3.創(chuàng)建相關(guān)文件如下:
vue-demo
|--build
|--webpack.base.js
|--webpack.dev.js
|--webpack.prod.js
|--src
|--static
|--images
|--scss
|--index.scss
|--views
|--app.vue
|--index.js
|--index.html
|--postcss.config.js
|--favicon.png
// webpack.base.js 公用配置文件
const webpack = require('webpack');
const path = require("path");
const VueLoaderPlugin = require('vue-loader/lib/plugin'); // vue-loader
const HtmlWebpackPlugin = require('html-webpack-plugin'); // html
module.exports = {
entry: {
index: path.resolve(__dirname, '../src/index.js'),
},
resolve: {
alias: { // 別名
'@src': path.resolve(__dirname, '../src'),
'@views': path.resolve(__dirname, '../src/views'),
'@scss': path.resolve(__dirname, '../src/static/scss'),
'@images': path.resolve(__dirname, '../src/static/images'),
},
extensions: ['.js', '.vue'], // 配置擴展名
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.(scss|css)$/,
use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'],
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/,
// 使用url-loader, 它接受一個limit參數(shù)廊散,單位byte桑滩;
// 當(dāng)文件小于limit:將文件轉(zhuǎn)為Data URI格式內(nèi)聯(lián)到引用的地方
// 當(dāng)文件大于limit:將調(diào)用 file-loader, 把文件復(fù)制到輸出目錄,并將引用的文件路徑改寫成輸出后的路徑
use: [
{
loader: 'url-loader',
options: {
limit: 20 * 1024,
// 分離圖片至imgs文件夾
name: "imgs/[name].[ext]",
}
},
]
},
]
},
plugins: [ // 插件
new VueLoaderPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '../src/index.html'), // html模板
favicon: path.resolve(__dirname, '../favicon.png'),
}),
],
};
// webpack.dev.js 開發(fā)環(huán)境配置文件
const path = require('path');
const merge = require('webpack-merge'); // 合并配置文件
const common = require('./webpack.base.js');
module.exports = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
devServer: { // 開發(fā)服務(wù)器
port: '3000',
// open: false, // 可以設(shè)置是否每次啟動都自動打開瀏覽器頁面
contentBase: '../dist',
host: '0.0.0.0', // 可通過IP訪問,也可以通過localhost訪問
useLocalIp: true, // browser open with your local IP
},
output: { // 輸出
filename: 'js/[name].[hash].js', // 每次保存 hash 都變化
path: path.resolve(__dirname, '../dist')
},
module: {},
});
// webpack.prod.js 生產(chǎn)環(huán)境的配置文件
const path = require('path');
const merge = require('webpack-merge');
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); // 清理dist文件夾
const common = require('./webpack.base.js');
module.exports = merge(common, {
mode: 'production',
output: {
filename: 'js/[name].[contenthash:8].js', // 若文件內(nèi)容無變化运准,則contenthash不變
path: path.resolve(__dirname, '../dist')
},
module: {},
plugins: [
new CleanWebpackPlugin(),
],
});
// index.js
import Vue from 'vue'
import App from './views/app.vue'
import '@scss/index.scss'
new Vue({
el: '#app',
render: h => h(App),
});
<!-- app.vue -->
<template>
<div id="app">
hello, vue-demo
</div>
</template>
<script>
export default {
name: 'app'
}
</script>
<style lang="scss" scoped>
#app {
text-align: center;
color: #333;
margin-top: 100px;
display: flex;
}
</style>
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>webpack-vue-demo</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
// postcss.config.js css兼容前綴
module.exports = {
plugins: {
'autoprefixer': {
overrideBrowserslist: [
'Android 4.1',
'iOS 7.1',
'Chrome > 31',
'ff > 31',
'ie >= 8'
]
}
}
}
4.npm命令:
"scripts": {
"start": "webpack-dev-server --hot --progress --config build/webpack.dev.js",
"build": "webpack --progress --config build/webpack.prod.js"
},
現(xiàn)在幌氮,執(zhí)行npm start
即可體驗項目啦~npm run build
可以打包項目
引入babel7
@babel/preset-env 語法裝換,配置 polyfill及按需加載胁澳;@babel/plugin-transform-runtime復(fù)用輔助函數(shù)
1.安裝依賴:
npm i babel-loader @babel/core @babel/cli --save-dev
npm i @babel/preset-env @babel/plugin-transform-runtime --save-dev
npm i @babel/polyfill @babel/runtime --save
2.配置loader:
// webpack.base.js
// module.rules中添加
{
test: /\.js$/,
use: ['babel-loader'],
exclude: /node_modules/, // 排除不要加載的文件夾
include: path.resolve(__dirname, '../src') // 指定需要加載的文件夾
},
3.配置babel该互,在根目錄下添加文件.babelrc.js
module.exports = {
presets: [
[
"@babel/preset-env",
{
"corejs": 2,
"modules": false, // 模塊使用 es modules 组砚,不使用 commonJS 規(guī)范
"useBuiltIns": 'usage', // 默認(rèn) false, 可選 entry , usage宝鼓;usage表示按需加載
}
]
],
plugins: [
[
"@babel/plugin-transform-runtime",
{
"corejs": false, // 默認(rèn)值蚣旱,可以不寫
"helpers": true, // 默認(rèn)周蹭,可以不寫
"regenerator": false, // 通過 preset-env 已經(jīng)使用了全局的 regeneratorRuntime, 不再需要 transform-runtime 提供的 不污染全局的 regeneratorRuntime
"useESModules": true, // 使用 es modules helpers, 減少 commonJS 語法代碼
}
],
]
}
引入其他工具及組件庫
eslint:
安裝依賴:npm i babel-eslint eslint eslint-friendly-formatter eslint-loader eslint-plugin-vue -D
// 根目錄下新建文件 .eslintrc.js:
module.exports = {
//一旦配置了root捎稚,ESlint停止在父級目錄中查找配置文件
root: true,
//想要支持的JS語言選項
parserOptions: {
//啟用ES6語法支持(如果支持es6的全局變量{env: {es6: true}}妇押,則默認(rèn)啟用ES6語法支持)
//此處也可以使用年份命名的版本號:2015
ecmaVersion: 6,
//默認(rèn)為script
sourceType: "module",
//支持其他的語言特性
ecmaFeatures: {},
parser: "babel-eslint"
},
//代碼運行的環(huán)境蕴坪,每個環(huán)境都會有一套預(yù)定義的全局對象聚假,不同環(huán)境可以組合使用
env: {
amd: true, // 否則會出現(xiàn)'require' is not defined 提示
es6: true,
browser: true,
jquery: true
},
//訪問當(dāng)前源文件中未定義的變量時锦庸,no-undef會報警告机蔗。
//如果這些全局變量是合規(guī)的,可以在globals中配置甘萧,避免這些全局變量發(fā)出警告
globals: {
//配置給全局變量的布爾值萝嘁,是用來控制該全局變量是否允許被重寫
test_param: true,
window: true,
},
//支持第三方插件的規(guī)則,插件以eslint-plugin-作為前綴扬卷,配置時該前綴可省略
//檢查vue文件需要eslint-plugin-vue插件
plugins: ["vue"],
//集成推薦的規(guī)則
extends: ["eslint:recommended", "plugin:vue/essential"],
globals: {
process: false,
},
//啟用額外的規(guī)則或者覆蓋默認(rèn)的規(guī)則
//規(guī)則級別分別:為"off"(0)關(guān)閉牙言、"warn"(1)警告、"error"(2)錯誤--error觸發(fā)時怪得,程序退出
rules: {
//關(guān)閉“禁用console”規(guī)則
"no-console": "off",
//縮進不規(guī)范警告嬉挡,要求縮進為2個空格,默認(rèn)值為4個空格
"indent": ["warn", 4, {
//設(shè)置為1時強制switch語句中case的縮進為2個空格
"SwitchCase": 1,
}],
// 函數(shù)定義時括號前面要不要有空格
"space-before-function-paren": [0, "always"],
//定義字符串不規(guī)范錯誤汇恤,要求字符串使用雙引號
// quotes: ["error", "double"],
//....
//更多規(guī)則可查看http://eslint.cn/docs/rules/
}
}
// webpack.base.js
// module.rules中添加
{
test: /\.(js|vue)$/,
loader: 'eslint-loader',
enforce: 'pre',
// 指定檢查的目錄
include: [path.resolve(__dirname, '../src')],
// eslint檢查報告的格式規(guī)范
options: {
formatter: require('eslint-friendly-formatter')
}
},
運行項目庞钢,根據(jù)eslint提示修改不規(guī)范的代碼
vue-router
npm i @babel/plugin-syntax-dynamic-import -D
npm i vue-router -S
// .babelrc.js
module.exports = {
plugins: [
"@babel/plugin-syntax-dynamic-import", // 支持路由懶加載:()=>import('...')
...
],
...
}
// src/index.js改成如下
import '@scss/index.scss';
import Vue from 'vue';
import router from '@src/router/index.js';
import App from '@views/app.vue';
new Vue({
el: '#app',
router,
render: h => h(App),
});
// 新增文件 src/router/index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import Test from '../views/test';
import NoFound from '@views/noFound';
Vue.use(VueRouter);
export default new VueRouter({
// mode: 'history', // 使用history防止url中出現(xiàn)#
routes: [
{
path: '/',
name: 'test',
component: Test
}, {
path: '/test1',
name: 'test1',
component: () =>
import(/* webpackChunkName: "test1" */ '@views/test1.vue'),
}, {
path: '*',
name: 'noFound',
component: NoFound
}
]
});
新增文件 src/views/app.vue
<template>
<div id="app">
<div class="header">
<router-link to="/">首頁</router-link>
<router-link to="/test1">test1</router-link>
<router-link to="/a">noFound</router-link>
</div>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'app',
};
</script>
<style lang="scss" scoped>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
text-align: center;
margin-top: 60px;
transform: rotate(0deg);
}
</style>
新增文件 src/views/noFound.vue
<template>
<div>
noFound
</div>
</template>
<script>
export default {
name: 'noFound',
};
</script>
新增文件 src/views/test.vue
<template>
<div>
首頁:test
<div>{{msg}}</div>
</div>
</template>
<script>
export default {
name: 'test',
data() {
return {
msg: '首頁信息'
}
},
};
</script>
修改文件 src/views/test1.vue成一下內(nèi)容;并且在src/static/images下面新增1.jpg,2.jpg,smart.gif
<template>
<div>
<div>
test1, count :
<span class="red">{{loading ? 'loading...' : count}}</span>
</div>
<div>
<div class="btn" @click="addCount">add count</div>
</div>
<div class="wrap">
<div>
<div>gif</div>
<img src="@images/smart.gif" alt="">
</div>
<div>
<div>1</div>
<img src="@images/1.jpg" alt="">
</div>
<div>
<div>2</div>
<img src="@images/2.jpg" alt="">
</div>
<div>
<div>3</div>
<div class="img-bg-1"></div>
</div>
<div>
<div>4</div>
<div class="img-bg-2"></div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'test',
data() {
return {
loading: false,
count: 1,
}
},
methods: {
addCount() {
if (this.loading) return ;
return new Promise((resolve) => {
this.loading = true;
setTimeout(() => {
const {count} = this;
this.count = count + 1;
this.loading = false;
resolve();
}, 2000);
});
},
},
};
</script>
<style lang="scss" scoped>
$red: #a00;
.red {
color: $red;
}
img {
width: 100px;
}
@mixin img-bg {
width: 100%;
height: 120px;
background-size: 100px;
}
.img-bg-1 {
background: url(~@images/1.jpg) no-repeat center top;
@include img-bg();
}
.img-bg-2 {
background: url(~@images/2.jpg) no-repeat center top;
@include img-bg();
}
.wrap {
display: flex;
&>div {
flex: 1;
}
}
</style>
修改src/static/scss/index.scss
.btn {
display: inline-block;
padding: 5px 10px;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
}
vuex
npm i vuex vuex-router-sync -S
// 修改src/index.js
...
import { sync } from 'vuex-router-sync';
import store from '@src/store/index';
// 鏈接vuex和vue-router
sync(store, router);
new Vue({
...
store, // 新增這一行
...
});
新增如下文件:
|--src
|--store
|--actions.js
|--getters.js
|--index.js
|--mutations.js
|--state.js
// actions.js
export const changeMsg = ({ commit }) => {
commit({
type: 'mutationsMsg', // 對應(yīng)mutation.js中的mutationsMsg方法
globalMsg: '我是修改后的全局?jǐn)?shù)據(jù)~~~'
});
};
// getters.js
export const gettersMsg = state => state.globalMsg;
// index.js
import Vue from 'vue';
import Vuex from 'vuex';
import * as actions from './actions';
import * as mutations from './mutations';
import * as getters from './getters';
import state from './state';
Vue.use(Vuex);
const store = new Vuex.Store({
state,
getters,
actions,
mutations
});
export default store;
// mutations.js
export const mutationsMsg = (state, payload) => {
state.globalMsg = payload.globalMsg;
}
// state.js
const state = {
globalMsg: '我是全局?jǐn)?shù)據(jù)',
}
export default state;
具體使用
// test.uve
<template>
<div>
首頁:test
<div>{{gettersMsg}}</div>
<div class="btn" @click="changeMsg">點擊改變數(shù)據(jù)</div>
</div>
</template>
<script>
import { mapGetters, mapActions } from 'vuex';
export default {
name: 'test',
data() {
return {};
},
computed: { ...mapGetters(['gettersMsg']) },
methods: { ...mapActions(['changeMsg']) }
};
</script>
antd
npm i babel-plugin-import less less-loader -D
npm i ant-design-vue -S
// .babelrc.js plugins數(shù)組中增加
plugins: [
...,
["import", { "libraryName": "ant-design-vue", "libraryDirectory": "es", "style": true }], // ant組件按需加載
]
// webpack.base.js rules中加入
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
{
loader: 'less-loader', // compiles Less to CSS
options: { // ant自定義主題
modifyVars: {
'primary-color': '#63937d',
'link-color': '#11b96c',
'item-hover-bg': '#547c6a',
'item-active-bg': '#466657',
},
javascriptEnabled: true,
},
}
]
}
// src/index.js 新增一句
import '@src/plugins';
// 新增文件 src/plugins/index.js
import './ant';
// 新增文件 src/plugins/ant/index.js
import Vue from 'vue';
import {
Button,
Icon,
Layout,
Breadcrumb,
Dropdown,
Divider,
Menu,
Pagination,
Steps,
Checkbox,
DatePicker,
Input,
InputNumber,
Radio,
Select,
Switch,
TimePicker,
Popover,
Tabs,
Tag,
Tooltip,
Alert,
message,
Modal,
Popconfirm,
Spin,
ConfigProvider,
LocaleProvider
} from 'ant-design-vue';
Vue.use(Button);
Vue.use(Icon);
Vue.use(Layout);
Vue.use(Breadcrumb);
Vue.use(Dropdown);
Vue.use(Divider);
Vue.use(Menu);
Vue.use(Pagination);
Vue.use(Steps);
Vue.use(Checkbox);
Vue.use(DatePicker);
Vue.use(Input);
Vue.use(InputNumber);
Vue.use(Radio);
Vue.use(Select);
Vue.use(Switch);
Vue.use(TimePicker);
Vue.use(Popover);
Vue.use(Tabs);
Vue.use(Tag);
Vue.use(Tooltip);
Vue.use(Alert);
Vue.use(Modal);
Vue.use(Popconfirm);
Vue.use(Spin);
Vue.use(ConfigProvider);
Vue.use(LocaleProvider);
message.config({
duration: 2,
maxCount: 3,
});
Vue.prototype.$message = message;
Vue.prototype.$Modal = Modal;
使用
// 修改views/test.vue
<a-button type="primary">點擊</a-button>
優(yōu)化
根據(jù)不同分環(huán)境配置參數(shù)因谎、設(shè)置全局變量基括、優(yōu)化打包信息
開發(fā)和生產(chǎn)環(huán)境都區(qū)分三種接口配置:本地、測試财岔、正式
// package.json scripts改成:
"dev:local": "webpack-dev-server --env.CUSTOM_ENV=local --hot --progress --config build/webpack.dev.js",
"dev:pre": "webpack-dev-server --env.CUSTOM_ENV=pre --hot --progress --config build/webpack.dev.js",
"dev": "webpack-dev-server --env.CUSTOM_ENV=pro --hot --progress --config build/webpack.dev.js",
"build:local": "webpack --progress --env.CUSTOM_ENV=local --config build/webpack.prod.js",
"build:pre": "webpack --progress --env.CUSTOM_ENV=pre --config build/webpack.prod.js",
"build": "webpack --progress --env.CUSTOM_ENV=pro --config build/webpack.prod.js"
// .eslintrc.js 新增配置
modules.exports = {
...
globals: {
...
process: false,
},
...
}
新增文件 build/_config.js
let config = {
isDev: false, // 是否為開發(fā)環(huán)境
mode: 'production', // webpack mode:production风皿、development
domain: '', // API
env: 'pro', // 對應(yīng)接口:local、pre匠璧、pro
};
module.exports = (env, mode) => {
config.isDev = mode === 'development';
config.mode = mode;
config.env = env.CUSTOM_ENV;
let domain = '';
switch(env.CUSTOM_ENV) {
case 'local': // 本地開發(fā)環(huán)境接口
domain = 'local-api.domain.com';
break;
case 'pre': // 測試環(huán)境接口
domain = 'pre-api.domain.com';
break;
default: // 正式環(huán)境接口
domain = 'api.domain.com';
}
config.domain = domain;
return config;
};
// 修改webpack.base.js
const getConfig = require('./_config');
module.exports = (env, mode) => {
const envConfig = getConfig(env, mode);
return {
stats: {
children: false, // 清理控制臺不必要的打印信息
},
// 沿用之前的配置
entry: {
...
},
...
plugins: [
...,
new webpack.DefinePlugin({ // 自定義全局變量
'process.env.CUSTOM_ISDEV': JSON.stringify(envConfig.isDev),
'process.env.CUSTOM_MODE': JSON.stringify(envConfig.mode),
'process.env.CUSTOM_DOMAIN': JSON.stringify(envConfig.domain),
'process.env.CUSTOM_ENV': JSON.stringify(envConfig.env),
}),
]
}
}
// 修改webpack.dev.js
module.exports = env => {
const mode = 'development';
const commonConfig = common(env, mode);
return merge(commonConfig, {
mode,
devtool: 'inline-source-map',
...
}
}
// 修改webpack.prod.js
module.exports = env => {
const mode = 'production';
const commonConfig = common(env, mode);
return merge(commonConfig, {
mode,
...
}
}
// src/index.js 新增
if (process.env.CUSTOM_MODE !== 'production') {
console.log('CUSTOM_ISDEV:', process.env.CUSTOM_ISDEV);
console.log('CUSTOM_MODE:', process.env.CUSTOM_MODE);
console.log('CUSTOM_DOMAIN:', process.env.CUSTOM_DOMAIN);
console.log('CUSTOM_ENV:', process.env.CUSTOM_ENV);
}
優(yōu)化打包信息:通過webpack.base.js中config.stats桐款、命令行中的--progress 優(yōu)化
可視化打包工具測試:webpack-bundle-analyzer
npm i webpack-bundle-analyzer -D
package.json的scripts中增加:
"build:stats": "webpack --progress --env.CUSTOM_ENV=pro --env.STATS --config build/webpack.prod.js --profile --json > stats.json"
修改webpack.prod.js:
...
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
...
plugins: [
...,
// 分析包大小
...(env.STATS ? [new BundleAnalyzerPlugin()] : [])
]
...
使用:npm run build:stats
生成stats.json文件,再在項目根目錄中執(zhí)行 webpack-bundle-analyzer 后夷恍,瀏覽器會打開對應(yīng)網(wǎng)頁可以看到相關(guān)分析結(jié)果
持久化緩存
npm i script-ext-html-webpack-plugin -D
// 修改webpack.dev.js
const webpack = require('webpack');
plugins: [
...,
new webpack.NamedModulesPlugin(), // 將文件路徑作為 id
]
// 修改webpack.prod.js
...
const webpack = require('webpack');
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');
const seen = new Set();
const nameLength = 4;
module.exports = env => {
...
return merge(commonConfig, {
...
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
libs: { // 基礎(chǔ)類庫:它是構(gòu)成我們項目必不可少的一些基礎(chǔ)類庫魔眨,比如 vue+vue-router+vuex+axios 這種標(biāo)準(zhǔn)的全家桶,它們的升級頻率都不高,但每個頁面都需要它們
name: 'chunk-libs',
test: /[\\/]node_modules[\\/]/,
priority: 10,
chunks: 'initial' // 只打包初始時依賴的第三方
},
antUI: { // UI 組件庫
name: 'chunk-ant', // 單獨將 ant 拆包
priority: 20, // 權(quán)重要大于 libs 和 app 不然會被打包進 libs 或者 app
test: /[\\/]node_modules[\\/]ant-design-vue[\\/]/
},
commons: { // 自定義組件/函數(shù)
name: 'chunk-commons',
test: path.resolve(__dirname, '../src/components/components-global'), // 可自定義拓展你的規(guī)則遏暴,比如注冊全局組件的目錄
minChunks: 2, // 最小共用次數(shù)
priority: 5,
reuseExistingChunk: true
},
}
},
// runtimeChunk:提取 manifest侄刽,使用script-ext-html-webpack-plugin等插件內(nèi)聯(lián)到index.html減少請求
runtimeChunk: true,
/*
* moduleIds: 固定moduleId;使用文件路徑的hash作為 moduleId朋凉,解決moduleId遞增變化導(dǎo)致的無法長期緩存問題
* 相當(dāng)于在plugins中使用new webpack.HashedModuleIdsPlugin()
*/
moduleIds: 'hashed',
},
...,
plugins: [
...,
// 注意一定要在HtmlWebpackPlugin之后引用, inline 的name 和runtimeChunk 的 name保持一致;將runtime~index.xxx.js內(nèi)聯(lián)到html中
new ScriptExtHtmlWebpackPlugin({
inline: /runtime.*\.js$/
}),
// NamedChunkPlugin:結(jié)合自定義nameResolver固定chunkId
new webpack.NamedChunksPlugin(chunk => {
if (chunk.name) {
return chunk.name;
}
const modules = Array.from(chunk.modulesIterable);
if (modules.length > 1) {
const hash = require('hash-sum');
const joinedHash = hash(modules.map(m => m.id).join('_'));
let len = nameLength;
while (seen.has(joinedHash.substr(0, len))) len++;
seen.add(joinedHash.substr(0, len));
return `chunk-${joinedHash.substr(0, len)}`;
} else {
return modules[0].id;
}
})
]
});
}
生產(chǎn)環(huán)境抽取css并壓縮優(yōu)化及js壓縮
npm i mini-css-extract-plugin optimize-css-assets-webpack-plugin uglifyjs-webpack-plugin -D
刪除webpack.base.js rules中關(guān)于scss和css相關(guān)的處理
// 修改webpack.base.js
...
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // 生產(chǎn)環(huán)境抽離css
module.exports = (env, mode) => {
const envConfig = getConfig(env, mode);
const isDev = envConfig.isDev;
return {
...,
module: {
...,
rules: [
...,
{
test: /\.(scss|css)$/,
include: [
path.resolve(__dirname, '../src')
],
use: [
isDev ? 'style-loader' : {
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../' // 讓css能成功加載到圖片
}
},
'css-loader', 'postcss-loader', 'sass-loader'
],
},
{
test: /\.less$/,
use: [
isDev ? 'style-loader' : {
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../'
}
},
'css-loader',
'postcss-loader',
{
loader: 'less-loader',
options: { // ant自定義主題
modifyVars: {
'primary-color': '#000000',
'link-color': '#17c9e6',
'item-hover-bg': '#F7F7F7',
'item-active-bg': '#f3f3f3',
},
javascriptEnabled: true,
},
}
],
}
]
},
...
}
}
// 修改webpack.prod.js
...
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // 抽離css
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); // css壓縮與優(yōu)化
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
...
optimization: {
...
minimizer: [
new UglifyJsPlugin({ // 壓縮js
cache: true,
parallel: true,
// sourceMap: true
}),
new OptimizeCSSAssetsPlugin(), // 壓縮css州丹,導(dǎo)致webpack4自帶的js壓縮無效,需添加UglifyJsPlugin
],
},
...
plugins: [
...
// 增加css抽取
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
// chunkFilename: 'css/[id].[contenthash:8].css'
}),
]
使用DllPlugin和DllReferencePlugin提升編譯速度
npm i add-asset-html-webpack-plugin -D
package.json scripts中增加:
"dll": "npm run _dll:pro && npm run _dll:dev",
"_dll:pro": "webpack --mode production --progress --config build/webpack.dll.js",
"_dll:dev": "webpack --mode development --progress --config build/webpack.dll.js",
// 新增文件 build/webpack.dll.js
const path = require('path');
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = (env, argv) => {
const isDev = argv.mode === 'development';
const dir = isDev ? '../dll/dev' : '../dll/pro';
return {
// mode: 'production', // ???????
entry: {
// 將數(shù)組中的模塊作為入口編譯成動態(tài)鏈接庫
'vendor': ['vue', 'vue-router', 'vuex', 'vuex-router-sync', '@ant-design/icons/lib/dist']
},
output: {
// 指定生成文件所在目錄
// 由于每次打包生產(chǎn)環(huán)境時會清空 dist 文件夾杂彭,因此這里我將它們存放在了dll文件夾下
path: path.resolve(__dirname, dir),
// 指定文件名并添加hash
filename: '[name].[contenthash:6].dll.js',
// 存放動態(tài)鏈接庫的全局變量名稱墓毒,加上 _dll_ 是為了防止全局變量沖突:例如對應(yīng) vendor 來說就是 _dll_vendor
// 這個名稱需要與 DllPlugin 插件中的 name 屬性值對應(yīng)起來
library: '_dll_[name]'
},
plugins: [
new CleanWebpackPlugin(),
// 接入 DllPlugin
new webpack.DllPlugin({
// 描述動態(tài)鏈接庫的 manifest.json 文件輸出時的文件名稱
path: path.join(__dirname, dir, '[name].manifest.json'),
// 動態(tài)鏈接庫的全局變量名稱,需要和 output.library 中保持一致
// 該字段的值也就是輸出的 manifest.json 文件 中 name 字段的值
// 例如 venfor.manifest.json 中就有 "name": "venfor_dll"
name: '_dll_[name]'
})
]
}
}
// 修改webpack.base.js
...
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
...
plugins: [
// 告訴 Webpack 使用了哪些動態(tài)鏈接庫
new webpack.DllReferencePlugin({
// 描述 vendor 動態(tài)鏈接庫的文件內(nèi)容
manifest: require(isDev ? '../dll/dev/vendor.manifest.json' : '../dll/pro/vendor.manifest.json')
}),
...,
// 在HtmlWebpackPlugin后使用:用于將vendor插入打包后的頁面亲怠,并將vendor移動到dist文件夾下面
new AddAssetHtmlPlugin([
{
// 要添加到編譯中的文件的絕對路徑
filepath: path.resolve(__dirname, isDev ? '../dll/dev/vendor.*.dll.js' : '../dll/pro/vendor.*.dll.js'), // 匹配到帶hash的文件
// 文件輸出目錄:會在dist文件夾下面再生成dll文件夾
outputPath: 'dll',
// 腳本或鏈接標(biāo)記的公共路徑
publicPath: 'dll',
includeSourcemap: false,
}
]),
]
首次使用需要執(zhí)行
npm run dll
構(gòu)建出測試/正式環(huán)境下的dll文件蚁鳖,html中會自動引入相應(yīng)的dll文件;以后只要沒有修改dll配置赁炎,就不需要重新構(gòu)建dll文件醉箕,只有修改了webpack.dll.js,才需要重新執(zhí)行npm run dll
徙垫。也就是按照上面的描述配置好后讥裤,先執(zhí)行npm run dll,得到dll文件夾下面的文件姻报,之后就可以和之前一樣按照npm run dev開發(fā)己英、npm run build打包了
這里之所以需要區(qū)分環(huán)境構(gòu)建不同dll是因為在mode為production時,devtools無法查看vuex數(shù)據(jù)吴旋,尚未找到其他解決方案
使用happypack多進程加速編譯
npm i happypack -D
// 修改webpack.base.js
...
const HappyPack = require('happypack');
// 構(gòu)造出共享進程池损肛,進程池中包含5個子進程
const happyThreadPool = HappyPack.ThreadPool({ size: 5 });
rules: [
...
// 修改babel-loader
{
test: /\.js$/,
use: 'happypack/loader?id=babel',
exclude: /node_modules/,
include: path.resolve(__dirname, '../src')
},
{
test: /\.less$/,
use: [
isDev ? 'style-loader' : {
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../'
}
},
'happypack/loader?id=less'
],
}
],
...
plugins: [
new HappyPack({
// 用唯一的標(biāo)識符 id 來代表當(dāng)前的 HappyPack 是用來處理一類特定的文件
id: 'babel',
// 如何處理 .js 文件,用法和 Loader 配置中一樣
loaders: ['babel-loader'],
// 使用共享進程池中的子進程去處理任務(wù)
threadPool: happyThreadPool,
}),
new HappyPack({
id: 'less',
loaders: ['css-loader', 'postcss-loader', {
loader: 'less-loader',
options: { // ant自定義主題
modifyVars: {
'primary-color': '#000000',
'link-color': '#17c9e6',
'item-hover-bg': '#F7F7F7',
'item-active-bg': '#f3f3f3',
},
javascriptEnabled: true,
},
}
],
threadPool: happyThreadPool,
}),
...
]
tips:這里沒有對scss的處理使用happypack荣瑟,因為使用后發(fā)現(xiàn)vue文件中style帶scoped會失效治拿,尚未找到原因及解決方案
這里使用了happypack之后構(gòu)建速度反而變慢了,原因笆焰?劫谅??
其他小調(diào)整
// 修改webpack.base.js
...
performance: { // 控制 webpack 如何通知「資源(asset)和入口起點超過指定文件限制」
hints: 'warning',
maxAssetSize: (isDev ? 20 : 1) * 1024 * 1024, // 單文件:bytes
maxEntrypointSize: (isDev ? 20 : 3) * 1024 * 1024, // 入口所有文件:bytes
},
...,
resolve: {
...,
modules: [path.resolve(__dirname, '../node_modules')], // 使用絕對路徑指明第三方模塊存放的位置嚷掠,以減少搜索步驟
},
src/index.js中要把
import '@src/plugins';
放到import '@scss/index.scss';
前面捏检,才不會讓ant的樣式覆蓋了自定義的樣式
學(xué)習(xí)目錄
<font size=3 color=#63937d>webpack相關(guān):</font>
- npm詳解
- 簡單易懂的 webpack 打包后 JS 的運行過程-1
- 簡單易懂的 webpack 打包后 JS 的運行過程-2
- import、require不皆、export贯城、module.exports 混合使用詳解
- webpack 打包機制
- webpack 應(yīng)用編譯優(yōu)化之路
- 深入淺出webpack
- 入門Webpack,看這篇就夠了
- webpack 優(yōu)化
- Webpack4 搭建 Vue 項目
- webpack4.X 實戰(zhàn)(二)
- webpack4.X 實戰(zhàn)(三)
- 搭建項目教程
- 使用 Webpack 的 DllPlugin 提升項目構(gòu)建速度
- 使用webpack4提升180%編譯速度
- 移動spa商城優(yōu)化記(二)--- 減少70%的打包等待時間
- postcss-loader autoprefixer
- 使用 HashedModuleIdsPlugin 解決 hash 頻繁變動的問題
- 手摸手霹娄,帶你用合理的姿勢使用webpack4(下)
- css壓縮
- vuex-router-sync
- ESLint(vue+webpack)配置
- 清理控制臺
- Webpack傳參
<font size=3 color=#63937d>遇到的問題:</font>
- babel的polyfill和runtime的區(qū)別
- Babel學(xué)習(xí)系列4-polyfill和runtime差別
- transform-runtime polyfill env
- 關(guān)于@babel/polyfill -- 按需加載
- babel7最佳實踐
- Babel 7 升級實踐
- webpack持久化緩存-1
- webpack持久化緩存-2
以下為本項目構(gòu)建過程中遇到的疑問查找到的相關(guān)參考:
1能犯、什么時候用babel-polyfill鲫骗,什么時候用babel-runtime?
(1)transform-runtime不會污染全局悲雳,但是不能使用實例方法挎峦,如Array.find
(2)babel-polyfill會污染全局空間香追,并可能導(dǎo)致不同版本間的沖突合瓢,而babel-runtime不會透典。從這點看應(yīng)該用babel-runtime。
但記住峭咒,babel-runtime有個缺點,它不模擬實例方法凑队,即內(nèi)置對象原型上的方法,所以類似Array.prototype.find漩氨,你通過babel-runtime是無法使用的西壮。最后,請不要一次引入全部的polyfills(如require('babel-polyfill'))叫惊,這會導(dǎo)致代碼量很大款青。請按需引用最好。
(3)按需引入polyfill存在風(fēng)險霍狰,可能無法為某些第三方組件提供其依賴的polyfill:https://juejin.im/post/5cb9833b6fb9a068a84fe4d0,
遺留問題:
1抡草、依賴包中tree-shaking后的依賴文件能夠被編譯嗎,文件中使用的es6新特性能夠被polyfill檢測到蔗坯?