关于Vue打包的遇到模板引擎解析的引号问题

首先我们先看下问题

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);

再次打包完美解决

相关推荐
qq_510351593 小时前
vw 和 clamp()
前端·css·html
良木林3 小时前
JS中正则表达式的运用
前端·javascript·正则表达式
JosieBook4 小时前
【SpringBoot】21-Spring Boot中Web页面抽取公共页面的完整实践
前端·spring boot·python
吃饭睡觉打豆豆嘛4 小时前
深入剖析 Promise 实现:从原理到手写完整实现
前端·javascript
前端端4 小时前
claude code 原理分析
前端
GalaxyMeteor5 小时前
Elpis 开发框架搭建第二期 - Webpack5 实现工程化建设
前端
Spider_Man5 小时前
从 “不会迭代” 到 “面试加分”:JS 迭代器现场教学
前端·javascript·面试
我的写法有点潮5 小时前
最全Scss语法,赶紧收藏起来吧
前端·css
小高0075 小时前
🧙‍♂️ 老司机私藏清单:从“记事本”到“旗舰 IDE”,我只装了这 12 个插件
前端·javascript·vue.js