首先我们先看下问题
bash
npm run build

工程配置文件
这是一个3年前的vue-cli工程,事情经过晚上有个组员问我,项目配置了这个多页面就打包错误,然后去排查了硬是没有定位到问题,于是我周六又来公司进行排查,由于项目工程大,还是被我发现了问题所在
js
'use strict'
const path = require('path')
const isProduction = process.env.NODE_ENV === 'development' ? false : true
const webpack = require('webpack')
const TerserPlugin = require('terser-webpack-plugin');
function resolve(dir) {
return path.join(__dirname, dir)
}
console.log('process.version=>>>',process.version)
const SITE = process.env.SITE
// 动态加载代理文件
const config = require('./config/index.js')
const proxy = config.getProxy(SITE)
const entry = ['hibidding'].includes(process.env.VUE_APP_MODE) ? 'examples/web-design/main.js' : 'examples/main.js';
const multiple = require("./src/build/multiple.js");
module.exports = {
productionSourceMap: false,
lintOnSave: false, //eslint开关
publicPath: process.env.VUE_APP_proxybaseurl||'/',
pages: multiple,
devServer: {
open: true,
port: 9000,
proxy: proxy
},
configureWebpack: (config) => {
// 生产环境配置
if (process.env.NODE_ENV === 'production') {
// 覆盖默认的 minimizer 配置
config.optimization = {
...config.optimization,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true, // 移除所有 console.*
drop_debugger: true, // 移除 debugger
pure_funcs: ['console.log'] // 也可以指定只移除某些 console 方法
},
format: {
comments: false // 移除注释
}
},
extractComments: false // 不提取注释到单独文件
})
]
}
}
return {
externals: {
AMap: 'AMap'
},
plugins:[
new webpack.DefinePlugin({
'process.env.SITE': JSON.stringify(process.env.SITE)
}),
new webpack.optimize.LimitChunkCountPlugin({
maxChunks:30
}),
],
resolve: {
alias: {
'@': resolve('src')
}
},
}
},
chainWebpack: (config) => {
config.resolve.alias
.set("@", resolve("src"))
.set('examples', path.resolve('../examples'))
.set('packages', path.resolve('../packages'))
config.module.rules.delete('images')
config.module.rules.delete('packages')
config.module.rules.delete('svg')
// 把 packages 和 examples 加入编译,因为新增的文件默认是不被 webpack 处理的
config.module
.rule('packages')
.include.add(/packages/).end()
.include.add(/examples/).end()
config.module
.rule('images')
.test(/\.(png|jpe?g|gif|svg)(\?.*)?$/)
.exclude.add(resolve('packages/icons')).end()
.use('url-loader')
.loader('url-loader')
.options({
limit: 10000,
name: 'img/[name].[hash:8].[ext]'
})
config.module
.rule('svg')
.test(/\.svg$/)
.include.add(resolve('packages/icons')).end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
},
transpileDependencies:['bpmn-js-token-simulation','bpmn-js-task-resize','bpmn-js-sketchy','vue-quill-editor','v-region','pm-utils','vue-tree-color',]
}
multiple.js文件
js
const indexEntry = ['hibidding'].includes(process.env.VUE_APP_MODE) ? 'examples/web-design/main.js' : 'examples/main.js';
const multiple = {
index: {
entry: indexEntry,
filename: 'index.html',
chunks: ['index']
},
pmOcxOptionCa: {
entry: 'src/html/ocxLogin/main.js',
chunks: ['ocxLogin'],
title:"IE登录",
template: 'public/pmOcxOptionCa.html'
},
pmOcxOptionSign: {
entry: 'src/html/ocxSign/main.js',
chunks: ['ocxSign'],
title:"IE签章",
template: 'public/pmOcxOptionSign.html'
},
};
const entry = {};
Object.keys(multiple).forEach((value) => {
if (!multiple[value].template) {
multiple[value].template = 'public/index.html'
}
multiple[value].filename = `${value}.html`
if (multiple[value].chunks.length) {
multiple[value].chunks = [...new Set(['chunk-vendors', 'chunk-common'].concat(multiple[value].chunks))]
}
multiple[value].icon = 'favicon.ico';
entry[value] = multiple[value];
});
module.exports = entry;
报错误信息
bash
Template execution failed: ReferenceError: name is not defined
ReferenceError: name is not defined
- pmOcxOptionCa.html:98
D:/pinming/gitlab/pm-framework-ui/public/pmOcxOptionCa.html:98:10
- pmOcxOptionCa.html:101 0cb4.module.exports
D:/pinming/gitlab/pm-framework-ui/public/pmOcxOptionCa.html:101:3
- index.js:284
[pm-framework-ui]/[html-webpack-plugin]/index.js:284:18
- runMicrotasks
- task_queues.js:95 processTicksAndRejections
internal/process/task_queues.js:95:5
经过的我在项目中一系列排查,定位到了html页面的这个函数
这个是html内容
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="IE=8,IE=9,IE=10" />
<title>工程建设电子交易系统--pdf文件签章</title>
</head>
<style>
*{
font-size: 14px;border:0;margin:0;padding:0;zoom:1;
}
body {
font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
font-size: 14px;
line-height: 1.15;
background-color: #FFFFFF;
}
#JGSignWraper{
padding:24px;
display: none;
}
.btn{
font-size: 14px;
padding: 6px 12px;
margin-right: 6px;
color: #FFFFFF;
background-color: #1890FF;
border-radius: 4px;
}
#overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
overflow: auto;
display: none;
justify-content: center;
align-items: center;
}
#loading-indicator {
border: 8px solid #f3f3f3;
border-top: 8px solid #3498db;
opacity: 0.6;
border-radius: 50%;
width: 60px;
height: 60px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
<body>
<div id="app"></div>
<div id="JGSignWraper">
<div style="padding:12px 30px 12px;text-align: right">
<button style="float:right;margin-right: 34px;margin-top:-6px" class="btn" type="button" onclick="saveSignFile()">保存签章文件</button>
</div>
<div id="pm_pdf_container"></div>
</div>
<div id="overlay">
<div id="loading-indicator"></div>
<iframe style="left: 0px; top: 0px; width: 100%; height: 100%; position: absolute; z-index: -1; background-color: transparent;"></iframe>
</div>
</body>
<script type="text/javascript">
var timer= null;
var signPDF = null;
var isIEBrowser = isIE();
var optConfig = {
id:'', // 操作id
data:'',
optCode:'1', // 1 待执行 2 已完成
optType:'2', // 1 登录 2 签章
caType:'' // 签章类型
};
var caInfo = {
signdata:'',
signCert:'',
cacode:'',
caname:'',
};
(function () {
if(!isIEBrowser){
alert('请在IE页面打开本页面');
return
}
var token= getUrlParamsByName('token',window.location.href)
if(token){
tokenLogin(token,function(){
initOptData()
})
}else{
initOptData()
}
})();
// 获取签章相关信息
function initOptData() {
// https://szzw-dev.pminfo.cn/suite/pmOcxOption.html?id=8ff4404bb3e64a188bfd5d96fa9ad31d&token=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdXBlciI6IjEiLCJjb21wYXRpYmxlSWQiOjE1Nzg4MzA1OTIxMDYxNjMsIm5pY2tuYW1lIjoi57O757uf566h55CG5ZGYIiwidXBkYXRlVGltZSI6bnVsbCwidXNlckFnZW50IjoiTW96aWxsYS81LjAgKFdpbmRvd3MgTlQgMTAuMDsgV2luNjQ7IHg2NCkgQXBwbGVXZWJLaXQvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lLzEyMi4wLjAuMCBTYWZhcmkvNTM3LjM2IiwidXNlcklkIjoiN1FFekNabG5nLy9nMDBkTk5LQkRmS0xOdThrTVNpMktzanlTQ2xDSVBtZHlqNDV3SUIrbUJnPT0iLCJ0aWQiOiI5OTllNGM0NGNiZmI0MTZkYTYyZWJhNDNlMTI4YWMzMSIsInVzZXJuYW1lIjoic3VwcGVyYWRtaW4iLCJzaWQiOiJmNGVlYTM3ZTQzNDM0NGE0YmMzYzc3ZjAzN2YzOGZiMiIsImp0aSI6IjVjMzk0ZGMzMjIxMzQ5YmJhZDMyOWZiZTdlYWVjZjc3Iiwic3ViIjoibWljcm9zZXJ2aWNlIiwiaXNzIjoicG1pbmZvIiwiaWF0IjoxNzM4OTg1Mjk3LCJleHAiOjE3MzkwNzE2OTcsIm5iZiI6MTczODg5ODg5N30.tWI0zKjTI88bmd3ZSMMVfdLJmgjKq4F4y_GqJUlkZi4
$.ajax({
url: zy.suiteBaseUrl + '/api/ocxOption/get?id=' + getUrlParamsByName('id',window.location.href),
type: "POST",
data: {},
dataType: 'JSON',
headers:{
'Content-Type':'application/json;charset=UTF-8',
'token':'dfcbc25fc06a4491adfafd1e6c4d74f7'
},
success: function (res) {
if(res.code != '1'){
alert(res.msg);
return
}
optConfig = res.data || {};
if(optConfig.data){
// 金格签章
if(optConfig.caType == 'jingge'){
JingGeQianZhangInit(optConfig)
return
}
}
},
error: function (res) {
console.log('error:'+res)
alert('接口错误,请联系管理员。')
}
})
}
// 更新签章相关信息
function updateOptData(optConfig,callback){
$.ajax({
type: "POST",
url: zy.suiteBaseUrl + '/api/ocxOption/update',
data: JSON.stringify(optConfig),
dataType: 'JSON',
headers:{
'Content-Type':'application/json;charset=UTF-8',
'token':'dfcbc25fc06a4491adfafd1e6c4d74f7'
},
success: function(res){
if(res.code != '1'){
alert(res.msg);
return
}
if(callback && typeof callback == 'function'){
callback(res.data)
}
},
error: function (res) {
console.log('error:'+res)
alert('接口错误,请联系管理员。')
}
});
}
// 保存签章文件
function saveSignFile(){
// 金格签章
if(optConfig.caType == 'jingge'){
JingGeSaveFile(optConfig)
return
}
}
// 金格签章初始化
function JingGeQianZhangInit(optConfig){
$("#JGSignWraper").show();
$('#pm_pdf_container').width(document.documentElement.clientWidth - 100);
$('#pm_pdf_container').height(document.documentElement.clientHeight - 100);
try{
signPDF = new PmUtils.pdf.Jingge();
signPDF.openUrlPDF({url: zy.contextPath + optConfig.data});
} catch (error) {
alert('请检查CA驱动是否安装成功');
}
}
// 金格签章保存服务器
function JingGeSaveFile(){
var sealNum = (signPDF && signPDF.getSealNum && signPDF.getSealNum({isOne: true})) || 0;
if(!sealNum){
alert('文件未签章不可保存。');
return
}
showLoading();
signPDF.pmUploadHttpPost({
url:JgfileUploadServeUrl,
pam:JSON.stringify({
paras:{
table:'signed_file',
}
}),
success:function(res){
hidenLoading();
if(res.code == '1'){
var resData = res.data ? jQuery.parseJSON(res.data) : {};
if(resData.filepath){
// 操作更新
optConfig.optCode = '2';
optConfig.data = resData.filepath;
updateOptData(optConfig,function(response){
alert('签章文件已上传至文件服务器,文件地址为【' + resData.filepath + '】');
window.close();
})
}else{
alert('文件上传失败。');
}
}
},
error:function(res){
hidenLoading()
alert('打开签章文件失败,请检查签章驱动。');
}
})
}
// 从URl中获取参数
function getUrlParamsByName(name, url) {
// \b 边界
// ?<= 向后匹配
// 字符串转成正则表达式,其中的'\b'类型的特殊字符要多加一个'\';
// new RegExp(`(?<=\\b${name}=)[^&]*`), target = url.match(reg);此方法IE不支持
var reg = new RegExp('(\\b'+name+'=)[^&]*'), target = url.match(reg);
if (target && target[0]) {
return ((target[0]).split("="))[1]
}
return '';
}
/**********等待层***********/
function showLoading() {
// $.messager.progress({
// title: "系统消息",
// text: "数据处理中请稍候...",
// });
// IEObjectRenderLevelFix()
document.getElementById('overlay').style.display = 'flex';
}
/**********隐藏等待层***********/
function hidenLoading() {
// $.messager.progress("close");
document.getElementById('overlay').style.display = 'none';
}
function IEObjectRenderLevelFix(){
if(isIEBrowser)$(".window-mask").html('<iframe style="left: 0px; top: 0px; width: 100%; height: 100%; position: absolute; z-index: -1; background-color: transparent;"></iframe>');
}
function isIE() {
if (!!window.ActiveXObject || "ActiveXObject" in window) return true;
else return false;
}
// token登录
function tokenLogin(token,callback){
$.ajax({
type: "POST",
url: zy.suiteBaseUrl + '/api/oauth/tokenLogin?token=' + token,
data: {},
dataType: 'JSON',
success: function(res){
if(res.code != '1'){
alert(res.msg);
return
}
setOauthAccessToken(token);
if(callback && typeof callback == 'function'){
callback(res.data)
}
}
});
}
function setOauthAccessToken(value) {
if (!value) {
value = ''
}
sessionStorage.setItem('oauth_access_token', value)
sessionStorage.setItem('access_token', value)
localStorage.setItem('oauth_access_token', value)
setCookie('oauth_access_token', value);
}
function setCookie(name, value, daysToExpire) {
if (daysToExpire === undefined || daysToExpire === '') {
daysToExpire = 0;
}
var date = new Date();
date.setTime(date.getTime() + (daysToExpire * 24 * 60 * 60 * 1000)); // 计算过期时间
var expires = "expires=" + date.toUTCString();
document.cookie = name + "=" + value + ";" + expires + ";path=/";
}
</script>
</html>
问题函数
当我注释一下这个函数,发现打包就可以成功,难道是函数重名了,应该也不是,难道是name转义了,看到这里有个正则,大概想到了这个模板解析问题
js
// 从URl中获取参数
function getUrlParamsByName(name, url) {
// \b 边界
// ?<= 向后匹配
// 字符串转成正则表达式,其中的'\b'类型的特殊字符要多加一个'\';
// new RegExp(`(?<=\\b${name}=)[^&]*`), target = url.match(reg);此方法IE不支持
var reg = new RegExp('(\\b'+name+'=)[^&]*'), target = url.match(reg);
if (target && target[0]) {
return ((target[0]).split("="))[1]
}
return '';
}
在vue-cli中用到的模板解析插件是
html-webpack-plugin
它的工作原理是html-webpack-plugin 会用 lodash 的模板引擎把整个 HTML 当成模板编译。模板把页面文本拼成 JS 源码里的单引号字符串:p += '...';
然后在遇到这段代码里字符串被拆成了三段并用加号拼接:'(\b' + name + '=)[^&]'。模板在处理引号转义时,会把内部的单引号当成"字符串边界"的候选,结果在生成的模板函数源码里很容易出现类似:'...(\b' + name + '=)[^&]...' 这种效果,导致 + name + 落在字符串之外,被当成变量去求值;
模板执行时,作用域里并不存在变量 name,于是抛出 ReferenceError: name is not defined
知道了问题所在,我们解决问题
替换为双引号包裹整段正则字符串,避免内层单引号打断模板字符串
js
// 获取 URL 参数
var reg = new RegExp("(\\b" + name + "=)[^&]*"), target = url.match(reg);
注意,这里的注释也要删掉,或者改成正常的 // new RegExp((?<=\\b${name}=)[^&]*
), target = url.match(reg);
再次打包完美解决