使用webpack搭建vue專案
有一句話叫“前人栽樹後人乘涼”,還有一句話叫“如果說我看得比別人更遠些,那是因為我站在巨人的肩膀上”。前一句是國人的俗語,後一句是那個發現了“萬有引力”定律的牛頓說的。為什麼要引用這兩句呢?是因為我剛開始用vue的時候,使用的是vue-cli來搭建vue專案,快速又好用;我剛開始用react的時候,使用的是create-react-app來搭建react專案,方便又省事。使用這些已有的腳手架來搭建專案,無可厚非,對於新手來說,也確實能快速構建,不做置評。
既然已經有了這些現成的腳手架了,為什麼我們還熱衷於自己來配置webpack來搭建構建專案呢?因為我們只有瞭解並學會了配置webpack,我們才能更好地在打包構建專案時將webpack的效能發揮到極致,才能根據自身專案的實際需求,配置有利於專案開發的各種工具、外掛,提高我們的開發效率。比如我們在打包專案時,可以分析哪些地方降低了webpack的打包速度,別人打包速度需要花去十多秒、二十多秒,而你能將打包的速度提升至幾秒,這就是你的優勢。當然,涉及到webpack的執行原理以及開發自己的loader或plugin就可以自行去學習了哈,本文只帶你配置一個webpack來搭建一個vue專案。
wepack作為一個“模組打包機”其實是依賴了龐大的外掛體系,外掛體系是webpack的核心,可以說,webpack的生態就是建立在眾多外掛之上的,而開發環境和生產打包環境依賴的外掛還是有所不同的,先以開發環境為例webpack.config.js:
const path = require('path'); const Webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const VueLoaderPlugin = require('vue-loader/lib/plugin'); const ProgressBarPlugin = require('progress-bar-webpack-plugin'); const resolve = (dir) => { return path.join(__dirname, '..', dir) } const assetsPath = (_path) => { return path.join('static', _path) } const isEnvProduction = process.env.NODE_ENV == "production", port = 3003; module.exports = { mode: 'development', devtool: 'source-map', entry: resolve('src'), output: { path: resolve('dist'), filename: isEnvProduction ? assetsPath('js/[name]-[hash].js') : '[name]-[hash].js', chunkFilename: isEnvProduction ? assetsPath('js/[name]-[chunkhash:5].min.js') : '[name]-[chunkhash:5].min.js', publicPath: '/', }, resolve: { extensions: ['*', '.js', '.vue'], //webpack2.x extensions[0]不能為空 resolve屬性中的extensions陣列中用於配置程式可以自行補全哪些檔案字尾 alias: { '@': resolve('src'), // 'vue$': 'vue/dist/vue.esm.js' }, }, //提取公共程式碼 optimization: { splitChunks: { cacheGroups: { commons: { test: /[\\/]node_modules[\\/]/, //表示預設拆分node_modules中的模組 name: "vendor", //提取出來的檔案命名 chunks: "all", //提取所有檔案的公共部分 minChunks: 2, //表示提取公共部分最少的檔案數 模組被引用>=2次,拆分至vendors公共模組 minSize: 0, //表示提取公共部分最小的大小 模組超過0k自動被抽離成公共模組 }, } } }, module: { rules: [ { test: /\.vue$/, use: ['vue-loader'], exclude: /node_modules/, }, { test: /\.js$/, loader: 'babel-loader', exclude: /node_modules/, query: { "presets": ["@babel/env"], "plugins": ["@babel/plugin-syntax-dynamic-import", "@babel/plugin-transform-runtime"], } }, { test: /\.(sa|sc|c)ss$/, use: [ MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader', ], }, { test: /\.(eot?.+|svg?.+|ttf?.+|otf?.+|woff?.+|woff2?.+)$/, use: 'file-loader?name=' + (isEnvProduction ? assetsPath('fonts/[name].[hash:8].[ext]') : 'fonts/[name].[hash:8].[ext]') }, { test: /\.(jpg|jpeg|png|gif|ico|svg)$/, loader: 'url-loader', options: { limit: 10000, name: isEnvProduction ? assetsPath('images/[name].[hash:8].[ext]') : 'images/[name].[hash:8].[ext]', } }, ], }, plugins: [ new ProgressBarPlugin(), new VueLoaderPlugin(), //ProvidePlugin是webpack的內建模組,使用ProvidePlugin載入的模組在使用時將不再需要import和require進行引入 new Webpack.ProvidePlugin({ _: 'lodash', }), new HtmlWebpackPlugin({ template: './src/index.html', //檔案路徑及名稱 filename: 'index.html', //輸出後的檔名稱 }), new MiniCssExtractPlugin({ filename: isEnvProduction ? assetsPath("css/[name]-[hash].css") : "css/[name]-[hash].css", chunkFilename: isEnvProduction ? assetsPath("css/[name]-[hash].css") : "css/[name]-[hash].css", //預設就是取的以id或name為開頭的css,所以可以加這行配置程式碼,也可以不加 }), ], devServer: { port, host: '0.0.0.0', open: `http://localhost:${port}`, stats: { hash: false, builtAt: false, version: false, modules: false, children: false, ////解決類似Entrypoint undefined = index.html和Entrypoint mini-css-extract-plugin = *的警告 entrypoints: false, colors: { green: '\u001b[32m', yellow: '\u001b[32m', } }, proxy: { '/': { target: '', changeOrigin: true } }, inline: true, compress: false, disableHostCheck: true, historyApiFallback: true, }, }
關於配置中用到的一些外掛的api就不一一展開詳解了,唯一需要說明的一點是,配置中所用到的外掛的版本基本都是最新的,而使用postcss-loader時,需要在專案的根目錄新建一個postcss.config.js檔案:
module.exports = {
plugins: {
'autoprefixer': {browsers: 'last 5 version'}
}
}
以上是開發環境的webpack配置,下邊是打包生產環境的配置webpack.product.config.js:
const path = require('path'); const config = require('./webpack.config'); const merge = require('webpack-merge'); const UglifyJsPlugin = require("uglifyjs-webpack-plugin"); const HtmlWebpackPlugin = require('html-webpack-plugin'); const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); //壓縮單獨的css檔案 const CleanWebpackPlugin = require('clean-webpack-plugin'); const ManifestPlugin = require('webpack-manifest-plugin'); //資源清單 const SpeedMeasurePlugin = require("speed-measure-webpack-plugin"); //監控打包檔案所花費的時間,方便具體的效能優化 const smp = new SpeedMeasurePlugin(); const PurifyCSSPlugin = require("purifycss-webpack"); //css tree-shaking 依賴外掛glob-all和purify-css const glob = require("glob-all"); module.exports = smp.wrap(merge(config, { mode: 'production', stats: config.devServer.stats, devtool: false, //當我們想在專案中require一些其他的類庫或者API,而又不想讓這些類庫的原始碼被構建到執行時檔案中,這在實際開發中很有必要。此時我們就可以通過配置externals引數來解決這個問題 externals: { 'vue': 'Vue', 'vuex': 'Vuex', 'moment': 'moment', 'vue-router': 'VueRouter', 'element-ui': 'ELEMENT', 'ant-design-vue': 'antd', //使用externals html裡需手動引入一下js,特別注意:還需額外引入moment.js,並放在antd之前,否則會報錯 'lodash': '_', }, optimization: { minimizer: [ new UglifyJsPlugin({ parallel: true, //使用多執行緒並行執行來提高構建速度,預設併發執行數量:os.cpus().length - 1 uglifyOptions: { compress: { inline: false, drop_console: true, //是否遮蔽掉控制檯輸出 }, } }), new OptimizeCSSAssetsPlugin() //壓縮css ] }, plugins: [ new ManifestPlugin(), new CleanWebpackPlugin(), new PurifyCSSPlugin({ paths: glob.sync([ // 要做CSS Tree Shaking的路徑檔案 path.resolve(__dirname, "../src/*.vue") ]) }), new HtmlWebpackPlugin({ template: './src/index.prod.html', //打包時需要的檔案路徑和名稱 filename: 'index.html', //打包輸出後的檔名稱 minify: { //壓縮html removeComments: true, //刪除註釋 collapseWhitespace: true //刪除空格 } }), ], }));
打包的配置中有幾點需要注意:
1、配置中有一個speed-measure-webpack-plugin
的外掛,可以監控打包檔案所花費的時間,方便具體的效能優化;
2、配置中加入了webpack-manifest-plugin
生成資源清單的外掛,這個外掛所生成的資源清單對服務端渲染SSR非常有用,服務端可以根據當前的manifest,引入css和js檔案;
3、配置中引入了purifycss-webpack
和glob-all
兩個外掛並依賴一個purify-css
外掛用來對css的tree-shaking。shake有搖動、抖動之意,言外之意就是通過抖動將專案中沒有使用卻定義了的js方法給刪除,降低打包後項目的體積,很形象哈。自webpack2開始,webpack就自帶了js的tree-shaking,卻沒有css的tree-shaking,所以我們就藉助了外掛來實現tree-shaking。
4、為了提高打包的速度以及降低打包後的專案體積,我們可以將專案中用到框架採用CDN的方式引入,從而將這部分框架排除在打包之外,而new HtmlWebpackPlugin配置項中的template的路徑引用的index.prod.html檔案就是採用CDN的方式引入的第三方的框架,區分了開發環境中的index.html。提升構建速度也可以通過DllPlugin和DLLReferencePlugin外掛來實現,具體配置可參考:https://www.cnblogs.com/lusongshu/p/8473318.html
vue的專案目錄:
react專案的webpack配置跟vue專案的webpack配置大同小異,這裡不再多說,最後奉上package.json:
{
"name": "webpackvue",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "cross-env BABEL_ENV=development webpack-dev-server --config config/webpack.config.js",
"build": "cross-env NODE_ENV=production webpack --config config/webpack.product.config.js"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.4.4",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.4.4",
"@babel/runtime": "^7.4.4",
"autoprefixer": "^9.5.1",
"babel-loader": "^8.0.6",
"babel-plugin-import": "^1.11.2",
"clean-webpack-plugin": "^2.0.2",
"cross-env": "^5.2.0",
"css-loader": "^2.1.1",
"file-loader": "^3.0.1",
"glob-all": "^3.1.0",
"html-webpack-plugin": "^3.2.0",
"lodash": "^4.17.11",
"mini-css-extract-plugin": "^0.6.0",
"node-sass": "^4.12.0",
"optimize-css-assets-webpack-plugin": "^5.0.1",
"postcss-loader": "^3.0.0",
"progress-bar-webpack-plugin": "^1.12.1",
"purify-css": "^1.2.5",
"purifycss-webpack": "^0.7.0",
"sass-loader": "^7.1.0",
"speed-measure-webpack-plugin": "^1.3.1",
"style-loader": "^0.23.1",
"uglifyjs-webpack-plugin": "^2.1.3",
"url-loader": "^1.1.2",
"vue-loader": "^15.7.0",
"vue-template-compiler": "^2.6.10",
"webpack": "^4.31.0",
"webpack-cli": "^3.3.2",
"webpack-dev-server": "^3.3.1",
"webpack-manifest-plugin": "^2.0.4",
"webpack-merge": "^4.2.1"
},
"dependencies": {
"ant-design-vue": "^1.3.9",
"element-ui": "^2.8.2",
"moment": "^2.24.0",
"vue": "^2.6.10",
"vue-router": "^3.0.6",
"vuex": "^3.1.1"
}
}