[前端][新点小程序] 为什么我们构建卡片小程序的时候,dev 环境可以运行,但是小程序上传后无法如期运行

新点小程序

基础知识

M8 框架-开发小程序卡片open in new window

M8 框架-开发小程序open in new window

大家可以查看此文档,学习基础知识后,查看下面的内容。

小程序卡片

很多开发人员在开发小程序卡片的时候,在跑dev开发环境时没有问题,但是通过卡片构建后,会发现本来在 dev 环境可以正常访问,但是在prod环境时,就会发现访问不了。

小程序卡片的构建

可以从构建命令中分析,在构建过程中到底发生了什么?

以下内容,依据 M8 框架carddemo模块的构建结果,来分析。

配置项 pulgin.json

[
    {
        // 小程序模块名字
        "moduleName": "carddemo",
        // 路径
        "indexURL": "carddemo/index",
        "cardId": "ua-69328137",
        "minEJSVersion": "3.5.0",
        "version": "1.0.1",
        // 类型
        "miniH5Type": "card",
        "updateType": "nexttime"
    }
]

当点击vscode插件小程序-上传的时候,则会在我们的终端执行以下命令

npm run build --  --target wc --name ua-69328137-1-0-1 src/pages/carddemo/index.vue

分析命令

vue-cli 文档open in new window来分析行命令

npm run build : 构建命令

-- : 环境模式(默认:prmoduction)

--traget wc: 允许你将项目中的任何组件以 Web Components 组件的方式进行构建,可通过查看文档open in new window来学习更多构建模式

wc 指的是 Web Component

--name ua-69328137-1-0-1:设置前缀,同时自定义元素的名称会由组件的文件名推导得出

src/pages/carddemo/index.vue: 需构建的目标文件

构建产物

我们可以发现,卡片的构建产物与常规小程序构建产物不同,多了个ua-xxxx-1-0-1.min.js

从 vue-cli 文档里可以看出这个 js 就是web-components文件,而且我们可以从 index.html看出来,卡片的构建产物是在js标签下的。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta
            content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, viewport-fit=cover"
            name="viewport"
        />
        <title></title>
        <!-- 加载vant.css -->
        <link rel="stylesheet" href="./css/vant.css" />
        <!-- 加载vue -->
        <script src="./lib/vue.js"></script>
        <!-- 加载js -->
        <script src="./ua-69328137-1-0-1.min.js"></script>
    </head>
    <body>
        <!-- 卡片的产物 -->
        <ua-69328137-1-0-1></ua-69328137-1-0-1>
    </body>
    <script>
        // 在卡片代码前加入css
        document
            .querySelector('ua-69328137-1-0-1')
            .shadowRoot.querySelector('div')
            .insertAdjacentHTML('beforeend', '<style>@import "./css/common.css";</style>');
        document
            .querySelector('ua-69328137-1-0-1')
            .shadowRoot.querySelector('div')
            .insertAdjacentHTML('beforeend', '<style>@import "./css/vant.css";</style>');
    </script>
</html>

构建流程

通过 webpack 进行构建,具体的构建流程如下:

// build/index.js
const { WebpackPlugin } = require('./tool');
...
// line: 21行
build : {
    configureWebpack: {
        plugins: [
            // 构建后操作dist目录,就是这个构建插件
            new WebpackPlugin(plugin),
        ]
    },
};

查看 WebpackPlugin 的代码,可以看出我们如何进行解析卡片小程序的代码

1.如果是wc的目标,则将对应路径进行切割,以及将对应 name 设置为 app 的 id

// build/tool.js
// line: 21行
if (argv.target === 'wc') {
    // 卡片模式
    // 将src/pages/carddemo/index.vue进行切割
    const cardName = process.argv[process.argv.length - 1].split('/');
    // 根目录为cardemo
    root = cardName[cardName.length - 2];
    // 将ua-69328137-1-0-1设置为appid
    appId = argv.name;
    // 卡片模式
    buildType = 'card';
    isDebug = /debug/.test(rootArr[3]);
} else if (rootArr.length > 3) {
    root = process.argv[process.argv.length - 1].substring(2);
    buildType = 'app';
} else {
    root = 'ALL';
}

2.解析plugin.json的配置项,然后判断是否构建完成,从上文可以知道 wc 目标构建可以生产一个ua-xxxx-1-0-1.js.min.js构建完成后再执行后续操作压缩

// build/tool.js
// line: 145行
if (buildType === 'card') {
    // 移动小程序卡片内置资源
    await fs.copy('./build/minih5inner/vue.js', './dist/lib/vue.js');
    await fs.copy('./build/minih5inner/css', './dist/css');
    await fs.copy('./public/', './dist', {
        filter(src) {
            if (/cardlist/.test(src) || /comdto/.test(src)) {
                return false;
            }

            return true;
        }
    });
    // target为plugin.json
    const cardId = target[0].cardId || target[0].appId;
    const cardVersion = isDebug ? 'debug' : target[0].version.replace(/\./g, '-') || '';

    // 创建小程序卡片页面模板
    await fs.outputFile(
        './dist/index.html',
        `
                        <!DOCTYPE html><html lang="en">
                        <head>
                        <meta charset="UTF-8">
                        <!-- H5页面窗口自动调整到设备宽度,并控制用户缩放页面 -->
                        <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0, viewport-fit=cover"        name="viewport" />
                            <title></title>
                            <link rel="stylesheet" href="./css/vant.css" />
                            <script src="./lib/vue.js"></script>
                            <script src="./${cardId}-${cardVersion}.min.js"></script>
                        </head>
                        <body>
                        <${cardId}-${cardVersion}></${cardId}-${cardVersion}>
                        </body>
                        <script>
                            document.querySelector('${cardId}-${cardVersion}').shadowRoot.querySelector('div').insertAdjacentHTML('beforeend', '<style>@import "./css/common.css";</style>')
                            document.querySelector('${cardId}-${cardVersion}').shadowRoot.querySelector('div').insertAdjacentHTML('beforeend', '<style>@import "./css/vant.css";</style>')
                        </script>
                        </html>`
    );
    //创建完成后,将对应的js文件加入
    const waitFileBuildFinish = function () {
        return new Promise((resolve) => {
            // 获取文件信息
            fs.stat(`./dist/${cardId}-${cardVersion}.min.js`, async function (err, stats) {
                //
                if (err) {
                    resolve(await waitFileBuildFinish());
                } else {
                    //文件大小
                    if (stats.size === 0) {
                        resolve(await waitFileBuildFinish());
                    } else {
                        setTimeout(() => {
                            resolve(stats.size);
                        }, 1000);
                    }
                }
            });
        });
    };
    // 等待.min文件构建完成后再执行后续操作
    const buildFinish = await waitFileBuildFinish();

    if (buildFinish > 0) {
        watcher.on('unlink', () => {
            this._zip('', root, async () => {
                await fs.moveSync(`./zip/${root}.zip`, `./dist/${root}.zip`);
            });
            watcher.close();
        });
        fs.remove(`./dist/${cardId}-${cardVersion}.js`);
    }
}

总结 & 解决方案

从分析命令、构建产物、构建流程这几块来查看,我们很好就能看出构建卡片流程中,我们从始至终都是构建主体是命令行中的[path],即src/pages/carddemo/index.vue,而非main.js/router.js,也就是说当我们构建一个卡片小程序的时候,我们并没有使用到main.js/router.js,虽然可能在dev模式下,我们是依靠main.js来初始化 vue 组件,但实际build wc后,我们只是得到了au-xxx.min.js的 Web Components 产物。

我们可以从src/pages/carddemo/index.vue文件内容看Util, Config都是通过'./cardboot'来获取,无需读取 main.js

import { Util, Config } from './cardboot';

所以我们需要注意点,在于我们在调试小程序卡片的时候(dev 命令),是通过 mian.js 构建产物的,但是通过小程序build构建,则是通单一的 vue 文件,所以在我们执行上传卡片小程序的时候,如果在 mian.js 做了什么业务操作(比如定义了全局变量、Vue.prototype的赋值),需要在 cardboot.js 中也需要进行重复操作。

最后更新时间::
贡献者: 吴松泽