項目需求制作為新的app的分享頁,故需要制作多頁面應用,那既然app是新的,這邊我們也要更新上,經過多方考察(度娘)下,綜合了一些他人的優點並結合項目實況產生了此文。
本文為了解釋詳細,篇幅可能會較長,並省去了部分初級操作。
ok,送上github地址 --- star,star,star我
項目目錄:
既然是webpack4,那就主要解釋下build中的文件
一. webpack.base.conf.js
公共配置文件:
const path = require('path'); const webpack = require("webpack"); const glob = require("glob"); require("./env-config"); // 分離css //消除冗余的css const purifyCssWebpack = require("purifycss-webpack"); // html模板 const htmlWebpackPlugin = require("html-webpack-plugin"); //靜態資源輸出 const copyWebpackPlugin = require("copy-webpack-plugin"); const rules = require("./webpack.rules.conf.js"); // 獲取html-webpack-plugin參數的方法 var getHtmlConfig = function (name, chunks) { return { template: `./src/pages/${name}/index.html`, filename: `${name}.html`, // favicon: './favicon.ico', // title: title, inject: true, hash: true, //開啟hash ?[hash] chunks: chunks, minify: process.env.NODE_ENV === "development" ? false : { removeComments: true, //移除HTML中的注釋 collapseWhitespace: true, //折疊空白區域 也就是壓縮代碼 removeAttributeQuotes: true, //去除屬性引用 }, }; }; function getEntry() { var entry = {}; //讀取src目錄所有page入口 glob.sync('./src/pages/**/*.js') .forEach(function (name) { var start = name.indexOf('src/') + 4, end = name.length - 3; var eArr = []; var n = name.slice(start, end); n = n.slice(0, n.lastIndexOf('/')); //保存各個組件的入口 n = n.split('/')[1]; eArr.push(name); entry[n] = eArr; }); return entry; }; module.exports = { entry: getEntry(), module: { rules: [...rules] }, resolve: { alias: { '@': path.resolve(__dirname, '../src') } },// 提取公共代碼 optimization: { splitChunks: { cacheGroups: { vendor: { // 抽離第三方插件 test: /node_modules/, // 指定是node_modules下的第三方包 chunks: 'initial', name: 'vendor', // 打包后的文件名,任意命名 // 設置優先級,防止和自定義的公共代碼提取時被覆蓋,不進行打包 priority: 10 } } } }, plugins: [//靜態資源輸出 new copyWebpackPlugin([{ from: path.resolve(__dirname, "../src/assets"), to: './assets', ignore: ['.*'] }]), // 消除冗余的css代碼 new purifyCssWebpack({ paths: glob.sync(path.join(__dirname, "../src/pages/*/*.html")) }), ] } //配置頁面 const entryObj = getEntry(); const htmlArray = []; Object.keys(entryObj).forEach(element => { htmlArray.push({ _html: element, title: '', chunks: ['vendor', element] }) }) //自動生成html模板 htmlArray.forEach((element) => { module.exports.plugins.push(new htmlWebpackPlugin(getHtmlConfig(element._html, element.chunks))); })
雖然有注釋,但是我還是會逐行解釋下,是不是很貼心...…… ^_^
const path = require('path'); const webpack = require("webpack"); const glob = require("glob"); require("./env-config"); //暫時先不管它,后面會講 // 分離css //消除冗余的css const purifyCssWebpack = require("purifycss-webpack"); // html模板 const htmlWebpackPlugin = require("html-webpack-plugin"); //靜態資源輸出 const copyWebpackPlugin = require("copy-webpack-plugin"); const rules = require("./webpack.rules.conf.js");
基本上就是一些變量的引用,簡單解釋一下glob和rules,glob是我們需要這個插件對我們多頁面的路徑做一個處理,這樣我們打包后才會生成相應的多個文件,而rules則是一些loader的配置,大家直接引用就好,此處就不多講了。
// 獲取html-webpack-plugin參數的方法 var getHtmlConfig = function (name, chunks) { return { template: `./src/pages/${name}/index.html`, filename: `${name}.html`, // favicon: './favicon.ico', // title: title, inject: true, hash: true, //開啟hash ?[hash] chunks: chunks, minify: process.env.NODE_ENV === "development" ? false : { removeComments: true, //移除HTML中的注釋 collapseWhitespace: true, //折疊空白區域 也就是壓縮代碼 removeAttributeQuotes: true, //去除屬性引用 }, }; }; function getEntry() { var entry = {}; //讀取src目錄所有page入口 glob.sync('./src/pages/**/*.js') .forEach(function (name) { var start = name.indexOf('src/') + 4, end = name.length - 3; var eArr = []; var n = name.slice(start, end); n = n.slice(0, n.lastIndexOf('/')); //保存各個組件的入口 n = n.split('/')[1]; eArr.push(name); entry[n] = eArr; }); return entry; };
這兩個方法比較重要,因為當我們使用多頁面打包的時候,在module.exports里的entry(此處所講內容皆在此文件中,下面同樣)中一般需要這樣配置
module.exports = { entry: { index: './src/pages/index/index.js' , page1: './src/pages/index/page1.js' , page2: './src/pages/index/page2.js' } //下面暫時忽略 /*...*/ }
這樣的話我們每添加一個文件就需要添加一項,頁面少還好,當頁面多了以后,無論是維護還是開發都很費勁,而且配置文件我們一般是不推薦做修改的。
而為了避免這樣的操作,我們就需要去定義這兩個方法來幫助我們
我們先來講getEntry,它實際上就是獲取到我們pages下各個頁面的index.js,然后返回一個對象,這樣我們就不用手動添加啦。
而getHtmlConfig則是用來配合htmlwebpackplugin的,htmlwebpackplugin需要一些配置,而我們是多頁面應用就需要產出多個同配置但是不同名的html文件,這個方法就是用我們傳入的參數而產生不同的頁面名配置。
眾所周知,在單頁面應用中,我們只需要一個index.html就可以了,但是在多頁面我們需要一一對應的頁面,而去一個個new htmlwebpackplugin也違反了我們的初衷
//配置頁面 const entryObj = getEntry(); const htmlArray = []; Object.keys(entryObj).forEach(element => { htmlArray.push({ _html: element, title: '', chunks: ['vendor', element] }) }) //自動生成html模板 htmlArray.forEach((element) => { module.exports.plugins.push(new htmlWebpackPlugin(getHtmlConfig(element._html, element.chunks))); })
我們的頁面是有規律的,也就是index.js對應相應的index.html,那我們就可以利用之前的getEntry來獲取到js文件,在生成對應的數組,利用gethtmlconfig,放入htmlwebpackplugin中就可以了。
二. webpack.dev.conf.js
開發環境配置文件:
const path = require('path'); const webpack = require("webpack"); const merge = require("webpack-merge"); const webpackConfigBase = require('./webpack.base.conf'); const webpackConfigDev = { mode: 'development', // 通過 mode 聲明開發環境 output: { path: path.resolve(__dirname, '../dist'), // 打包多出口文件 filename: './js/[name].bundle.js' }, devServer: { contentBase: path.join(__dirname, "../src"), publicPath:'/', host: "127.0.0.1", port: "8090", overlay: true, // 瀏覽器頁面上顯示錯誤 // open: true, // 開啟瀏覽器 // stats: "errors-only", //stats: "errors-only"表示只打印錯誤: hot: true, // 開啟熱更新 //服務器代理配置項 proxy: { '/test/*':{ target: 'https://www.baidu.com', secure: true, changeOrigin: true } } }, plugins: [ //熱更新 new webpack.HotModuleReplacementPlugin(), new webpack.DefinePlugin({ 'process.env.BASE_URL': '\"' + process.env.BASE_URL + '\"' }) ], devtool: "source-map", // 開啟調試模式 } module.exports = merge(webpackConfigBase, webpackConfigDev);
引入所需
webpack-merge,用來合並我們的webpack.base.conf.js和webpack.dev.conf.js
proxy,因為我們啟動dev環境的話,是在本地調試,會出現跨域的問題,proxy為我們做一層代理,解決跨域難題。
webpack.DefinePlugin, 后面我們在講
三. webpack.prod.conf.js
生產環境配置文件:
const path = require('path'); const webpack = require("webpack"); const merge = require("webpack-merge"); // 清除目錄等 const cleanWebpackPlugin = require("clean-webpack-plugin"); const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') const extractTextPlugin = require("extract-text-webpack-plugin"); const webpackConfigBase = require('./webpack.base.conf'); process.env.NODE_ENV = "test" const webpackConfigProd = { mode: 'production', // 通過 mode 聲明生產環境 output: { path: path.resolve(__dirname, '../dist'), // 打包多出口文件 filename: './js/[name].[hash].js', publicPath: './' }, devtool: 'cheap-module-eval-source-map', plugins: [ //刪除dist目錄 new cleanWebpackPlugin(['dist'], { root: path.resolve(__dirname, '../'), //根目錄 // verbose Write logs to console. verbose: true, //開啟在控制台輸出信息 // dry Use boolean "true" to test/emulate delete. (will not remove files). // Default: false - remove files dry: false, }), new webpack.DefinePlugin({ 'process.env.BASE_URL': '\"' + process.env.BASE_URL + '\"' }), // 分離css插件參數為提取出去的路徑 new extractTextPlugin({ filename: 'css/[name].[hash:8].min.css', }), //壓縮css new OptimizeCSSPlugin({ cssProcessorOptions: { safe: true } }), //上線壓縮 去除console等信息webpack4.x之后去除了webpack.optimize.UglifyJsPlugin new UglifyJSPlugin({ uglifyOptions: { compress: { warnings: false, drop_debugger: false, drop_console: true } } }) ], module: { rules: [] }, } module.exports = merge(webpackConfigBase, webpackConfigProd);
引入所需
cleanWebpackPlugin, 我們每次build后都會產出許多不同名文件(hash不同),但我們是不需要之前的文件的,利用這個插件來清除掉我們之前的dist文件
好了,這兩個文件比較簡單,就不多解釋了...
四. env-config.js
一般情況下,我們配置到這個地步就已經可以使用了,但因為項目需求我們需要配置超過2個的環境變量(webpack默認兩個development和production)
而我們不同的環境可能需要不同的接口:
ps: text ---> 請求test-api ,
dev ---> 請求dev-api,
pro ---> 請求api,
...
這時我們就需要利用前面所沒有講的webpack.DefinePlugin了,這個插件是用來聲明全局變量的,我們依據不同的打包命令定義不同的接口名稱。
'use strict' const path = require('path') /* * 環境列表,第一個環境為默認環境 * envName: 指明現在使用的環境 * dirName: 打包的路徑,只在build的時候有用 * baseUrl: 這個環境下面的api 請求的域名 * assetsPublicPath: 靜態資源存放的域名,未指定則使用相對路徑 * */ const ENV_LIST = [ { //開發環境 envName: 'dev', dirName: 'dev', baseUrl: 'http://100.xxx.xxx', assetsPublicPath:'/' }, { //測試環境 envName: 'test', dirName: path.resolve(__dirname, '../dist'), baseUrl: 'http://111.xxx.xxx', assetsPublicPath: '/' }, { //生產環境(命令行參數(process.arg)中prod是保留字,所以使用pro) envName: 'pro', dirName: path.resolve(__dirname, '../dist'), baseUrl: 'http://122.xxx.xxx', assetsPublicPath:'/' }, ] const argv = JSON.parse(process.env.npm_config_argv).original || process.argv const HOST_ENV = argv[2] ? argv[2].replace(/[^a-z]+/ig,"") : '' //沒有設置環境,則默認為第一個 const HOST_CONF = HOST_ENV ? ENV_LIST.find(item => item.envName === HOST_ENV) : ENV_LIST[0] // 把環境常量掛載到process.env方便客戶端使用 process.env.BASE_URL = HOST_CONF.baseUrl // process.env.ENV_NAME = HOST_CONF.envName module.exports.HOST_CONF = HOST_CONF module.exports.ENV_LIST = ENV_LIST
我們聲明一個數組,里面用來存放我們的環境變量,在將獲取到的環境變量掛載到process.env上,如我所寫的話,我們在客戶端直接console.log(process.env.BASE_URL)就是當前環境了。
那么程序怎么知道我們打包的是哪個環境呢?那就要去package.json中去做文章了
"scripts": { "test": "npm run build --[test]", "dev": "cross-env NODE_ENV=development webpack-dev-server --config build/webpack.dev.conf.js", "build": "cross-env NODE_ENV=production webpack --config build/webpack.prod.conf.js" },
這里我只做了test環境的配置,其他的只要配置npm run build --[xxx]即可,這里提醒一點,dev和build個人覺得其實不應該算是兩個環境變量,應該是你打包的手段(原諒我只能這樣解釋),你可以理解為一個是debug環境,一個是上線環境。
而前面沒有說的其實就是webpack.base.config.js引入我們的變量,而dev和prod中在去將我們需要的變量聲明全局啦。
ok,到這里基本就可以快樂的編寫你的頁面啦。
結束啦~結束啦~...