本项目 GitHub 仓库地址:https://github.com/charleylla/charley-vue-multi
去年九月份,我写了一个使用 Webpack3 配置多页的系列文章,下面是文章地址:
使用 webpack3 配置多页应用(一)
使用 webpack3 配置多页应用(二)
使用 webpack3 配置多页应用(三)
使用 webpack3 配置多页应用(四)
在这之前,我还写过一个使用 Webpack2 搭建 React 应用脚手架的文章:
搭建基于 webpack2 的 react 脚手架
现在,我去了新的公司,公司需要我搭建一个多页应用脚手架,作为公司多页应用的通用解决方案。由于公司的主要技术栈是 Vue,我就琢磨着使用 Webpack + Vue 搭建这样一个脚手架。
对于 Vue 的多页应用方案,网上有很多文章,其中有一些是基于 vue-cli 生成的单页应用进行修改,我看了这些文章觉得有点麻烦,正好之前也搭过类似的脚手架,就决定再重复造个轮子。
一年间,很多朋友阅读了我的 《使用 webpack3 配置多页应用》系列文章,并提供了一些宝贵的建议,我将这些建议应用到了新的多页应用脚手架中。新的脚手架,使用了 Webpack4。如果不打算使用 Zero Config(事实上使用自定义配置的灵活性更强,可以完成更多个性化的需求),Webpack4 和 Webpack3 没有太大的区别,但还是有部分配置发生了变化,我在写文章的时候会指出来。
Webpack 的配置确实麻烦,从 Webpack2 到 Webpack4,每次配置都要重新学习一些知识,幸运的是每次学习的时间都比上一次要少。搭建 Webpack2 的脚手架,花了大概两个星期,搭建 Webpack3 的脚手架,花了不到一周,而搭建 Webpack4 的脚手架,花了大约两天时间。这说明 Webpack2 到 Webpack4 在配置层面变动并不大(内核层面变动很大)多配置几次,就慢慢熟悉了。
建议大家先看 《使用 webpack3 配置多页应用》这系列的文章,可以对使用 Webpack 构建多页应用和常用 Loader 及 Babel 等工具的用法有个了解,本系列文章是在前面文章的基础上进行整合改进的结果,再包含了一些前端架构上的内容。
项目目录一览
下面是本项目的一个整体目录及说明:
│ .babelrc
│ .editorconfig
│ .eslintrc.js
│ .gitignore
│ package-lock.json
│ package.json
│ postcss.config.js
│ README.md
│ webpack.config.js
│
├─build —— Webpack 配置文件
│ alias.js
│ bundle.js
│ externals.js
│ loaders.js
│ webpack.config.base.js
│ webpack.config.dev.js
│ webpack.config.prod.js
│
├─doc —— 项目文档
│ README.md
│
├─src —— 项目源代码
│ ├─assets —— 通用的库/图片/样式文件
│ │ ├─image
│ │ ├─lib
│ │ └─style
│ │ atom.scss
│ │ constant.scss
│ │ func.scss
│ │ main.scss
│ │
│ ├─component —— 组件
│ │ └─test
│ │ index.js
│ │ style.scss
│ │ template.vue
│ │
│ ├─model —— 数据,接口请求
│ │ index.js
│ │
│ ├─page —— 页面
│ │ ├─home —— 每个页面一个文件夹,文件夹名字为页面名字
│ │ │ index.js
│ │ │ style.scss
│ │ │ template.vue
│ │ │
│ │ └─index
│ │ index.js
│ │ style.scss
│ │ template.vue
│ │
│ ├─shared —— 公用配置,如 API,常量等
│ │ api.js
│ │ constant.js
│ │
│ └─util —— 帮助,封装通用的方法
│ index.js
│ request.js
│
└─template —— 项目模板文件
config.js
favicon.ico
index.html
基础配置
下面介绍几个基础配置,主要是 Babel,ESLint 和 PostCSS 的配置内容。
- .babelrc文件
{
"presets": [
"env"
],
"plugins": [
[
"transform-runtime",
{
"polyfill": false,
"regenerator": true
}
]
]
}
- postcss.config.js 文件
module.exports = {
plugins: {
"autoprefixer": {
browsers: ["last 5 version","Android >= 4.0"],
//是否美化属性值 默认:true
cascade: true,
//是否去掉不必要的前缀 默认:true
remove: true
}
}
}
- .eslintrc.js 文件
module.exports = {
"env": {
"browser": true,
"commonjs": true,
"es6": true,
"node": true,
},
"extends": [
"eslint:recommended",
"plugin:vue/essential"
],
"parserOptions": {
"ecmaVersion": 8,
"sourceType": "module"
},
"rules": {
"comma-dangle": ["warn", "always-multiline"],
"indent": ["warn", 2],
"linebreak-style": ["warn", "unix"],
"quotes": ["warn", "double"],
"semi": ["warn", "always"],
"no-unused-vars": ["warn"],
"no-console": "warn",
},
};
- .editorconfig 文件
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
Webpack 配置文件
下面介绍脚手架 Webpack 的配置,也是本文的重点。
入口文件
使用根目录下的 webpack.config.js 作为 Webpack 配置的入口文件:
const env = process.env.ENVIROMENT.trim();
const option = process.env.OPTION ? process.env.OPTION.trim() : "";
const webpackConfigFn = require(`./build/webpack.config.${env}`);
module.exports = webpackConfigFn(env,{ option })
首先,获取两个环境变量:ENVIROMENT
和 OPTION
,这两个环境变量在 package.json 中,通过 cross-env
传入:
...
"scripts": {
"dev": "cross-env ENVIROMENT=dev webpack-dev-server --open",
"build": "cross-env ENVIROMENT=prod webpack",
"build:report": "cross-env OPTION=report npm run build",
"serve": "http-server ./dist -o"
},
...
使用 cross-env
是为了解决 Windows 和 Linux 下设置环境变量的方式不一致的问题,通过统一的方式设置环境变量。
ENVIROMENT
环境变量用来标识生产或者开发环境,OPTION
环境变量用来进行一些选项的设置,后文进行介绍。
ENVIROMENT
环境变量有两个:dev
和 prod
,通过这个环境变量决定导入 webpack.config.dev.js 或者 webpack.config.prod.js。
在 webpack.config.dev.js 和 webpack.config.prod.js 文件中,导出的是一个函数而不是一个配置对象,通过函数的方式具有更大的灵活性,可以接收参数,然后根据参数生成不同的配置文件。
基础配置文件
使用 webpack.config.base.js 作为 Webpack 的基础配置文件,包含一些公用的配置,该文件导出的也是一个函数,可以接收环境变量参数。
下面是 webpack.config.base.js
文件的内容:
const CleanWebpackPlugin = require("clean-webpack-plugin")
const VueLoaderPlugin = require("vue-loader/lib/plugin")
const CopyWebpackPlugin = require("copy-webpack-plugin");
const { initConfig,resolve } = require("./bundle")
const { initLoader } = require("./loaders")
const config = {
devtool: "cheap-module-source-map",
// 加载器
module: {
rules: [],
},
resolve:{
mainFields: ["jsnext:main", "browser", "main"]
},
plugins: [
new CleanWebpackPlugin(["dist"], {
root: resolve(""),
verbose: true, //开启在控制台输出信息
dry: false,
}),
new VueLoaderPlugin(),
new CopyWebpackPlugin([{
from: resolve("template"),
to: resolve("dist"),
ignore:["*.html"]
}]),
],
}
module.exports = function(env){
const {
entry,
output,
alias,
htmlPlugins
} = initConfig(env)
const loaders = initLoader(env);
config.entry = entry;
config.output = output;
config.resolve.alias = alias;
config.module.rules.push(...loaders);
config.plugins.push(...htmlPlugins)
return config;
}
可见,在 webpack.base.js 中,并没有直接对入口出口以及各种 Loader 以及 Alias 等进行配置,而是从 bundle.js 和 loaders.js 中导入了几个函数,通过为函数传参产生相应的配置,并添加到配置文件上。
拆分配置文件
通过 webpack.base.js 文件可以看到,我把一些配置写到 bundle.js 中了。该文件用来产生 Entry,Output,Alias 和 Plugins 的配置项,这样做的好处,首先是减少了 webpack.base.js 文件的大小,然后通过对配置文件进行拆分,使得后期修改配置文件很方便,不用直接和 webpack.base.js 打交道,只需要调整 bundle.js 中的函数即可,扩展性更好一点。
下面就来看 bundle.js 文件的内容:
const fs = require("fs")
const path = require("path")
const HTMLWebpackPlugin = require("html-webpack-plugin")
const alias = require("./alias")
const resolve = (p) => path.resolve(__dirname,"..",p)
const entryDir = resolve("src/page")
const outputDir = resolve("dist")
const templatePath = resolve("template/index.html")
const entryFiles = fs.readdirSync(entryDir)
const
entry = {},
output = {}
htmlPlugins = [];
// Map alias
function resolveAlias(){
Object.keys(alias).forEach(attr => {
const val = alias[attr]
alias[attr] = resolve(val)
})
}
// Handle Entry and Output of Webpack
function resolveEntryAndOutput(env){
entryFiles.forEach(dir => {
entry[dir] = resolve(`${entryDir}/${dir}`)
if(env === "dev"){
output.filename = "js/[name].bundle.js";
}else{
output.filename = "js/[name].bundle.[hash].js";
}
output.path = outputDir;
})
}
// Handle HTML Templates
function combineHTMLWithTemplate(){
entryFiles.forEach(dir => {
const htmlPlugin = new HTMLWebpackPlugin({
filename:`${dir}.html`,
template:templatePath,
chunks:[dir,"vendor"]
})
htmlPlugins.push(htmlPlugin)
})
}
function initConfig(env){
resolveAlias();
resolveEntryAndOutput(env);
combineHTMLWithTemplate();
return{
entry,
output,
alias,
htmlPlugins
}
}
exports.initConfig = initConfig;
exports.resolve = resolve;
resolve
方法用来解析路径,对 path.resolve
进行了一层包装。
alias
是 Webpack 配置的别名,从 alias.js 中导入,用来提供快捷的文件导入,避免 ../../../../xxx
这样的情况。下面是 alias.js 文件的内容:
const alias = {
"@component":"src/component",
"@util":"src/util",
"@shared":"src/shared",
"@model":"src/model",
"@assets":"src/assets",
}
module.exports = alias;
对于入口和出口文件的设置,首先通过 fs.readdirSync
方法读取 src/page
目录下的子目录,每个子目录都是一个页面,然后根据读取到的目录以及环境变量来设置 Webpack 的 Entry 和 Output。设置 Entry 和 Output 需要用到 resolveEntryAndOutput
方法。
对于 html-webpack-plugin
插件的模板,也根据 src/page
下的子目录自动生成。
最后导出一个 initConfig
函数,包含了基础的 Entry,Output,Alias 和 Plugins 配置项。
然后导出了前面定义的 resolve
方法,供其他文件使用。
拆分 Loaders
loaders.js 中包含了各种 Loader,下面是 loaders.js 文件的内容:
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { resolve } = require("./bundle")
const vueLoader = {
test: /\.vue$/,
use: "vue-loader"
}
const cssLoader = {
test: /\.css$/,
exclude: /node_modules/,
use: [
"vue-style-loader",
"css-loader",
"postcss-loader"
]
}
const sassLoader = {
test: /\.scss$/,
exclude: /node_modules/,
use: [
"vue-style-loader",
"css-loader",
"sass-loader",
"postcss-loader",
{
loader: "sass-resources-loader",
options: {
resources: resolve("src/assets/style/main.scss"),
},
}
]
}
const jsLoader = {
test: /\.js$/,
exclude: /node_modules/,
use: ["babel-loader"]
}
const imgLoader = {
test: /\.(png|svg|jpg|gif)$/,
use: {
loader: "file-loader",
options: {
name: "[name].[ext]",
outputPath:"img"
}
}
}
const fontLoader = {
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: {
loader: "file-loader",
options: {
outputPath:"font"
}
}
}
const eslintLoader = {
test: /\.(js|vue)$/,
enforce: "pre",
exclude: /node_modules/,
loader: "eslint-loader",
options: {
fix:true,
emitWarning:true,
}
}
exports.initLoader = function(env){
const loaders = [];
if(env !== "dev"){
cssLoader.use = [
MiniCssExtractPlugin.loader,
"css-loader",
"postcss-loader"
];
sassLoader.use = [
MiniCssExtractPlugin.loader,
"css-loader",
"sass-loader",
"postcss-loader",
{
loader: "sass-resources-loader",
options: {
resources: resolve("src/assets/style/main.scss"),
},
}
]
}else{
loaders.push(eslintLoader)
}
loaders.push(
vueLoader,
cssLoader,
sassLoader,
jsLoader,
imgLoader,
fontLoader
);
return loaders;
}
在 Webpack4 中,推荐使用 mini-css-extract-plugin
插件来提取 CSS 文件(老版本使用 extract-text-webpack-plugin
进行提取),在暴露的 initLoader
函数中,对环境变量进行了判断,如果是生产环境,就使用 mini-css-extract-plugin
对 CSS 进行提取。
还有一个 sass-resources-loader
,这个 Loader 用来为 SASS 提供全局的变量和函数,Mixins等功能。
对于图片和字体文件,使用 file-loader
将他们提取到 img 和 font 目录。这里我选择 file-loader
而不是 url-loader
的原因是使用 url-loader
会将图片打包成 Base64,插入到 JS 和 CSS 文件中,导致打包后的 JS 和 CSS 文件过大。
Externals 配置
使用 Externals,可以让 Webpack 打包时忽略某些库,直接使用 CDN 上的资源,有效的减轻了打包文件的体积。
Externals 配置放在 externals.js 文件中:
exports.externals = {
"vue": "Vue",
"axios": "axios"
}
开发环境配置文件
说完基础的配置文件,再看开发环境的配置文件就简单多了。开发环境的配置文件,主要是在基础配置文件的基础上,对 Webpack Dev Server 和热更新进行配置:
const webpackMerge = require("webpack-merge");
const webpack = require("webpack");
const { resolve } = require("./bundle")
const webpackBaseFn = require("./webpack.config.base");
module.exports = function(env){
const baseConfig = webpackBaseFn(env)
return webpackMerge(baseConfig,{
mode:"development",
devServer:{
contentBase:resolve("dist"),
host:"0.0.0.0",
useLocalIp: true,
overlay:{
errors:true,
warnings:true
},
open:true,
hot:true,
historyApiFallback: true,
inline: true,
disableHostCheck: true,
stats:{
assets: false,
chunks: false,
chunkGroups: false,
chunkModules: false,
chunkOrigins: false,
modules: false,
moduleTrace: false,
source: false,
builtAt: false,
children: false,
hash:false,
},
},
plugins:[
//热更新
new webpack.HotModuleReplacementPlugin(),
],
});
}
下面对 devServer
配置进行一些说明:
-
host
将host
指定为"0.0.0.0"
,就可以通过 IP 地址来访问 Webpack Dev Server 提供的服务了,处于安全问题的考虑,Webpack Dev Server 默认禁止了通过 IP 地址访问服务。 -
useLocalIp
该配置项和open
配置结合在一起使用,将useLocalIp
设置为true
后,自动打开浏览器时将会通过 IP 地址访问服务,如果不设置useLocalIp
,自动打开浏览器将会打开0.0.0.0:8080
。 -
stat
该配置项主要对控制台的输出进行一些清理工作,默认的控制台打印的日志很乱,通过对stat
进行配置后,控制台输出的日志清爽多了。
生产环境配置文件
下面是生产环境的配置文件 webpack.config.prod.js:
const webpackMerge = require("webpack-merge");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
const { resolve } = require("./bundle")
const { externals } = require("./externals")
const webpackBaseFn = require("./webpack.config.base");
module.exports = function(env,{ option }){
const baseConfig = webpackBaseFn(env)
const reportOn = option === "report"
const plugins = [
new MiniCssExtractPlugin({
filename: "css/[name].[hash].css",
}),
];
if(reportOn){
plugins.push(new BundleAnalyzerPlugin())
}
return webpackMerge(baseConfig,{
mode:"production",
optimization:{
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: "vendor",
chunks: "all"
}
}
},
minimizer: [
new UglifyJsPlugin({
uglifyOptions: {
compress: {
warnings: false,
drop_debugger: false,
drop_console: true
}
}
}),
new OptimizeCSSAssetsPlugin({
cssProcessorOptions: {
safe: true
}
})
]
},
stats:{
chunkGroups: false,
chunkModules: false,
chunkOrigins: false,
modules: false,
moduleTrace: false,
source: false,
children: false,
},
externals,
plugins
});
}
生产环境的配置文件中,主要对公用的 JS 和 CSS 进行了压缩提取,例外提供了报表功能:当将 OPTION
环境变量设置为 report
时,将会在构建完成后自动打开打包报表分析,可以分析打包中出现的问题,以及各个 Thunk 的大小。
在 Webpack4 中,代码分割和压缩 JS 以及 CSS 的配置放到了 optimization
选项中,splitChunks
用来提供代码分割功能,minimizer
用来提供压缩功能。
package.json
下面是 package.json 文件的内容:
{
"name": "CHARLEY_MUTLI_KIT",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "cross-env ENVIROMENT=dev webpack-dev-server",
"build": "cross-env ENVIROMENT=prod webpack",
"build:report": "cross-env OPTION=report npm run build",
"serve": "http-server ./dist -o"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"autoprefixer": "^8.6.4",
"babel-core": "^6.26.3",
"babel-loader": "^7.1.4",
"babel-plugin-transform-async-to-generator": "^6.24.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.7.0",
"clean-webpack-plugin": "^0.1.19",
"copy-webpack-plugin": "^4.5.2",
"cross-env": "^5.2.0",
"css-loader": "^0.28.11",
"eslint": "^5.0.1",
"eslint-loader": "^2.0.0",
"eslint-plugin-vue": "^4.5.0",
"file-loader": "^1.1.11",
"friendly-errors-webpack-plugin": "^1.7.0",
"html-webpack-plugin": "^3.2.0",
"http-server": "^0.11.1",
"mini-css-extract-plugin": "^0.4.1",
"node-sass": "^4.9.0",
"optimize-css-assets-webpack-plugin": "^4.0.3",
"postcss-loader": "^2.1.5",
"sass-loader": "^7.0.3",
"sass-resources-loader": "^1.3.3",
"style-loader": "^0.21.0",
"uglifyjs-webpack-plugin": "^1.2.7",
"url-loader": "^1.0.1",
"vue-loader": "^15.2.4",
"vue-style-loader": "^4.1.0",
"vue-template-compiler": "^2.5.16",
"webpack": "^4.14.0",
"webpack-bundle-analyzer": "^2.13.1",
"webpack-cli": "^3.0.8",
"webpack-dev-server": "^3.1.4",
"webpack-merge": "^4.1.3"
},
"dependencies": {
"axios": "^0.18.0",
"vue": "^2.5.16"
}
}
配置总结
本次在 Webpack 的配置中,我对配置文件中常用的功能进行了提取,将改动频率大的配置项单独出来,方便了扩展和维护,功能也更加清晰。此外,将 Node 和 Webpack 的配置结合的更加紧密,有了很大的灵活性,如果以后想做一个 React 的脚手架,只需要修改下 loaders.js 即可,不用动 Webpack 配置文件的主体。
Webpack 的配置部分就到此结束,下篇文章我给大家介绍下项目中的模块划分。
本项目 GitHub 仓库地址为:https://github.com/charleylla/charley-vue-multi,如果您觉得我的文章对您有帮助,欢迎帮我点个 Star,如果您有问题,欢迎提 Issue~
完。
网友评论