基于 vue-cli 搭建多模块且各模块独立打包

项目目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|-- public 静态资源目录(图片、文字等)
|-- images 全局共用的img图片目录
|-- js 存放JavaScript脚本
|-- css 全局公用的样式表
|-- index.html 首页入口文件(可添加meta 统计代码等)
|-- dist 打包项目
|-- [package] 项目包名
|-- node_modules npm 加载的项目依赖
|-- src
|-- assets
|-- config 项目配置文件(包括OSS地址等公共用常量配置文件)
|-- less.js css 中使用的全局变量,如服务器上的图片 OSS_PATH
|-- images 项目图片
|-- utils js文件(全局api,公共方法,组件注册)
|-- components 公共组件
|-- phjtWebTools 项目公共组件,方法库
|-- views 开发项目位置
|-- base
|-- [package]
|-- assets
|-- config 项目配置文件
|-- css 当前项目公共样式文件
|-- utils 当前项目公共js文件,如 vuex | Vue.observable 的注入
|-- views 开发主体页面
|-- router vue路由的目录
|-- store Vuex
|-- App.vue 项目文件入口
|-- main.js 项目的核心文件
|-- templates
|-- [package].html
|-- envs 根据具体项目的不同公共配置
|-- package.json 项目的配置信息(比如名称、版本、许可证等元数据)
|-- vue.config.js 项目配置文件
|-- README.md

修改 webpack 配置

代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
// vue.config.js

const path = require("path");
require("babel-polyfill");
const fs = require("fs");
const CompressionWebpackPlugin = require("compression-webpack-plugin");
const argv = require("yargs").argv;

function resolve(dir) {
return path.join(__dirname, dir);
}
const PRODUCTION = process.env.NODE_ENV === "production";

// 0开发 1正式环境 2测试服务器
const serverType = PRODUCTION ? (argv["test_server"] ? 2 : 1) : 0;

// APP 版本号(影响打包路径)
const APP_VERSION = (process.env.VUE_APP_VERSION = process.env.npm_package_appVersion);

// H5 版本号(影响打包文件名)
const H5_VERSION = (process.env.VUE_APP_H5_VERSION = process.env.npm_package_version);

// H5 包名称(只有生产环境和单项目开发环境才有值)
process.env.VUE_APP_NAME = "";
process.env.VUE_APP_VERSION_CODE = "";

// 生产环境使用CDN加速域名,开发和测试环境使用OSS域名
process.env.VUE_APP_HOST = serverType == 1 ? process.env.VUE_APP_CDN_HOST : process.env.VUE_APP_OSS_HOST;

let lessVars = require("./src/assets/config/less");
lessVars["TEST_SERVICE"] = process.env.VUE_APP_TEST_SERVICE = serverType; // css 中使用的全局变量,如服务器上的图片 OSS_PATH

console.log(">>>>>>>> 运行环境 ", ["开发环境", "正式环境", "测试服务器"][serverType]);

// 获取所有多入口文件
const getPages = (arr = [], toName) => {
const dirs = fs.readdirSync("./src/views");
let pages = {};
for (const name of dirs) {
if (arr.length > 0 && !arr.includes(name)) continue;
let temPath = `templates/${name}.html`;
let entryPath = `src/views/${name}/main.js`;
let newName = name;
if (arr.length == 1 && toName) {
newName = toName;
}
pages[newName] = {
entry: entryPath, // 入口
template: fs.existsSync(temPath) ? temPath : "public/index.html",
filename: PRODUCTION ? `index.html` : `${newName}.html`,
title: newName,
// 在这个页面中包含的块,默认情况下会包含,提取出来的通用 chunk 和 vendor chunk。
chunks: ["chunk-vendors", "chunk-common", newName]
};
}
return pages;
};
let proPathName = PRODUCTION ? argv["test_server"] || process.argv[3] : "";
let proNewName = (argv["test_server"] ? process.argv[5] : process.argv[4]) || proPathName;

console.log(process.argv);
let devPathName = "";
let page = {};
if (!PRODUCTION) {
if (argv["project"] && argv["project"] !== true) {
devPathName = String(argv["project"]).trim().split(",");
console.log(">>>>>>>> 运行项目 ", devPathName);
page = getPages(devPathName);
if (devPathName.length == 1) {
process.env.VUE_APP_NAME = devPathName[0];
process.env.VUE_APP_VERSION_CODE = require(`./src/views/${devPathName[0]}/assets/config/index`).versionCode;
}
} else {
console.log(">>>>>>>> 运行项目 ", "全部");
page = getPages();
}
} else if (proPathName) {
proPathName = proPathName.replace(/[^a-zA-Z|_\d]/gi, "");
proNewName = proNewName.replace(/[^a-zA-Z|_\d]/gi, "");
console.log(">>>>>>>> 打包项目 ", proPathName, proNewName);
process.env.VUE_APP_NAME = proNewName;
process.env.VUE_APP_VERSION_CODE = require(`./src/views/${proPathName}/assets/config/index`).versionCode;
page = getPages([proPathName], proNewName);
} else {
throw new Error("未写项目包名");
}

let cssOptions = PRODUCTION
? {
extract: {
filename: `css/[name]-${H5_VERSION}-[hash:8].css`,
chunkFilename: `css/[name]-${H5_VERSION}-[hash:8].css`
}
}
: {};
module.exports = {
publicPath: !PRODUCTION ? "/m" : "./",
outputDir: "./dist/" + proNewName,
filenameHashing: true,
pages: page,
productionSourceMap: false, // 生产环境 sourceMap
devServer: {
openPage: `${proNewName || ""}.html`,
open: true, // 项目构建成功之后,自动弹出页面
host: "0.0.0.0", // 主机名,也可以127.0.0.0 || 做真机测试时候0.0.0.0
port: 8082, // 端口号,默认8080
https: false, // 协议
hotOnly: false, // 没啥效果,热模块,webpack已经做好了
proxy: {
"/api": {
// target: "https://test-mall-web.peogoo.com",
target: "http://10.20.39.122:8082",
changeOrigin: true, // 如果接口跨域,需要进行这个参数配置
pathRewrite: {
"^/api": ""
}
}
// "/foo": {
// target: "<other_url>"
// }
} // 配置多个代理
},
// 配置less全局变量
css: {
loaderOptions: {
less: {
globalVars: lessVars
}
},
...cssOptions
},
configureWebpack: (config) => {
// ES6转为ES5
if (PRODUCTION && proPathName) {
config.entry[proNewName] = ["babel-polyfill", ...config.entry[proNewName]];
config.output.filename = `js/[name]-${H5_VERSION}-[hash:8].js`;
config.output.chunkFilename = `js/[name]-${H5_VERSION}-[hash:8].js`;
}

let plugins = [];
config.plugins.push(
new CompressionWebpackPlugin({
// 正在匹配需要压缩的文件后缀
test: /\.(js|css|svg|woff|ttf|json|html)$/,
// 大于10kb的会压缩
threshold: 10240
// 其余配置查看compression-webpack-plugin
})
);
// 生成环境且非测试服务器需要移除console和debugger
if (serverType == 1) {
console.log(`>>>>>>>> 移除console和debugger`);
config.plugins = [...config.plugins, ...plugins];
config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true; // 删除console
config.optimization.minimizer[0].options.terserOptions.compress.drop_debugger = true; // 删除deubgger
}
},
chainWebpack: (config) => {
//忽略的打包文件
config.externals({
"animate.css": "animate.css",
vue: "Vue",
"vue-router": "VueRouter",
vuex: "Vuex",
axios: "axios",
moment: "moment",
vant: "vant",
"vue-clipboard2": "vue-clipboard2"
});

// 别名
config.resolve.alias
.set("@", resolve("src"))
.set("@assets", resolve("src/assets"))
.set("@views", resolve("src/views"))
.set("@components", resolve("src/components"))
.set("@phjtWebTools", resolve("src/phjtWebTools"))
.set("@utils", resolve("src/assets/utils"));

config.module
.rule("svg")
.use("file-loader")
.loader("file-loader")
.options({ name: `img/[name]-${H5_VERSION}-[hash:8].[ext]` });

// 打包文件格式
config.module
.rule("images")
.test(/\.(png|jpe?g|gif|webp)(\?.*)?$/)
.use("url-loader")
.loader("url-loader")
.tap((options) => {
options.fallback.options.name = `img/[name]-${H5_VERSION}-[hash:8].[ext]`;
options.limit = 5120; //这是字节(Byte)限制,1KB = 1024Byte。
return options;
});

// 为了补删除换行而加的配置
config.module
.rule("vue")
.use("vue-loader")
.loader("vue-loader")
.tap((options) => {
options.compilerOptions.preserveWhitespace = true;
return options;
});
}
};

package.json

1
2
3
4
5
6
7
{
"script": {
"dev": "vue-cli-service serve --project",
"build": "vue-cli-service build",
"test": "vue-cli-service build --test_server"
}
}

构建指令

1
2
3
npm run build // 打包全部模块到一个资源包下面,每个模块的入口是module.html文件,静态资源都在static目录中,这种方式可以复用重复的资源
npm run test [moduleName] // 测试环境打包指定模块到一个资源包下面
npm run build [moduleName] // 生产环境打包打包指定模块到一个资源包下面