This is the seventh day of my participation in the August More text Challenge. For details, see:August is more challenging

background

A project is initialized as the VUE3 document uses the Vite template (figure 1 below), but is found not to take effect when the dynamic configuration items are configured (figure 2 below). Therefore, vsCode is used to debug the execution process of Vite. It is found that the version of Vite is too early to support the dynamic configuration feature in the latest document.

Figure 1

Figure 2

start

First, the project generated by running the yarn create vite-app XXX command does not contain the default vite configuration file. In this case, the default configuration is used when the vite is started. If you want to explicitly configure vite, you need to manually create it in the project root directory as follows:

vite.config.js

export default ({ command, mode }) => {
  if (command === 'build') {
    return {
      base: '/github_cleaner/'}}}Copy the code

Take a look at the package.json file at this point, as shown below

{
  "name": "github_cleaner"."version": "0.0.0"."scripts": {
    "dev": "vite"."build": "vite build"
  },
  "dependencies": {
    "vue": 14 "" ^ 3.0.0 - beta.
  },
  "devDependencies": {
    "vite": "^ 0.16.6"."@vue/compiler-sfc": 14 "" ^ 3.0.0 - beta.}}Copy the code

Then use the above configuration to run yarn build, that is, vite build, and find that the compiled index. HTML file (below) does not change the deployment directory to /github_cleaner/ as expected

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <link rel="icon" href="/_assets/favicon.59ab3a34.ico" />
  <title>Vite App</title>
<link rel="stylesheet" href="/_assets/style.9e6be708.css">
</head>
<body>
  <div id="app"></div>
  
<script type="module" src="/_assets/index.2f8e9066.js"></script>
</body>
</html>

Copy the code

debugging

Let’s debug the execution of vite to see why our dynamic configuration didn’t work. Try adding a breakpoint to the function we exported in vite.config.js and start debugging. It doesn’t stop at the breakpoint.

Look at node_modules’ vite package package.json, as shown below

{
  "name": "vite"."version": "0.16.12"."license": "MIT"."author": "Evan You"."bin": {
    "vite": "bin/vite.js"
  },
  "main": "dist/index.js"."types": "dist/index.d.ts"."files": [
    "bin"."dist/**/*.js"."dist/**/*.d.ts"."hmr.d.ts"],... }Copy the code

Since we are using the vite executable to start vite execution, the corresponding bin property is bin/vite.js. Find the file. The source code is as follows

#! /usr/bin/env node
require('.. /dist/cli')
Copy the code

The vite executable is executed using node_modules/vite/dist/cli.js.

Where the key initialization is as follows

const argv = require('minimist')(process.argv.slice(2)); .const config_1 = require("./config");
const command = argv._[0];
const defaultMode = command === 'build' ? 'production' : 'development';
Copy the code

Find the first of these immediately execute functions source code below, where we will set the first breakpoint

(async() = > {const { help, h, mode, m, version, v } = argv;
    if (help || h) {
        logHelp();
        return;
    }
    else if (version || v) {
        // noop, already logged
        return;
    }
    const envMode = mode || m || defaultMode;
    const options = await resolveOptions(envMode);
    if(! options.command || options.command ==='serve') {
        runServe(options);
    }
    else if (options.command === 'build') {
        runBuild(options);
    }
    else if (options.command === 'optimize') {
        runOptimize(options);
    }
    else {
        console.error(chalk_1.default.red(`unknown command: ${options.command}`));
        process.exit(1);
    }
})();
Copy the code

The resolveOptions function is used to parse the vite execution configuration

async function resolveOptions(mode) {
    // specify env mode
    argv.mode = mode;
    // shorthand for serviceWorker option
    if (argv['sw']) {
        argv.serviceWorker = argv['sw'];
    }
    // map jsx args
    if (argv['jsx-factory']) {; (argv.jsx || (argv.jsx = {})).factory = argv['jsx-factory'];
    }
    if (argv['jsx-fragment']) {; (argv.jsx || (argv.jsx = {})).fragment = argv['jsx-fragment'];
    }
    // cast xxx=true | false into actual booleans
    Object.keys(argv).forEach((key) = > {
        if (argv[key] === 'false') {
            argv[key] = false;
        }
        if (argv[key] === 'true') {
            argv[key] = true; }});// command
    if (argv._[0]) {
        argv.command = argv._[0];
    }
    // normalize root
    // assumes all commands are in the form of `vite [command] [root]`
    if(! argv.root && argv._[1]) {
        argv.root = argv._[1];
    }
    if (argv.root) {
        argv.root = path_1.default.isAbsolute(argv.root) ? argv.root : path_1.default.resolve(argv.root);
    }
    const userConfig = await config_1.resolveConfig(mode, argv.config || argv.c);
    if (userConfig) {
        return{... userConfig, ... argv// cli options take higher priority
        };
    }
    return argv;
}
Copy the code

const userConfig = await config_1.resolveConfig(mode, argv.config || argv.c); This is the key line

Find config_1 to start the file by const config_1 = require(“./config”); The introduction of

Here’s the logic before we enter the resolveConfig:

  1. performvite build
  2. buildAs acommandParameter isviteExecutable file parsing
  3. Set inside themodeProperty as in executionviteIs not explicitly declared whenmode, so usebuildCommand by defaultproduction
  4. With themode=productionParameters to performconfig.jsIn theresolveConfigfunction

Const argv = require(‘minimist’)(process.argv.slice(2)); Argv is the second and subsequent argument to parse when executing vite.

Argv does not have any value, so it is undefined when reading argv.config and argv.c.

The design here is that when executing vite, you can pass the vite configuration by carrying the parameter –config= XXX or –c= XXX

In-depth configuration resolution

First node_modules/vite/dist/config. About resolveConfig of js function implementation is as follows:

async function resolveConfig(mode, configPath) {
    const start = Date.now();
    const cwd = process.cwd();
    let config;
    let resolvedPath;
    let isTS = false;
    if (configPath) {
        resolvedPath = path_1.default.resolve(cwd, configPath);
    }
    else {
        const jsConfigPath = path_1.default.resolve(cwd, 'vite.config.js');
        if (fs_extra_1.default.existsSync(jsConfigPath)) {
            resolvedPath = jsConfigPath;
        }
        else {
            const tsConfigPath = path_1.default.resolve(cwd, 'vite.config.ts');
            if (fs_extra_1.default.existsSync(tsConfigPath)) {
                isTS = true; resolvedPath = tsConfigPath; }}}if(! resolvedPath) {return;
    }
    try {
        if(! isTS) {try {
                config = require(resolvedPath);
            }
            catch (e) {
                if (!/Cannot use import statement|Unexpected token 'export'/.test(e.message)) {
                    throwe; }}}if(! config) {// 2. if we reach here, the file is ts or using es import syntax.
            // transpile es import syntax to require syntax using rollup.
            const rollup = require('rollup');
            const esbuilPlugin = await buildPluginEsbuild_1.createEsbuildPlugin(false{});const bundle = await rollup.rollup({
                external: (id) = > (id[0]! = ='. ' && !path_1.default.isAbsolute(id)) ||
                    id.slice(-5, id.length) === '.json'.input: resolvedPath,
                treeshake: false.plugins: [esbuilPlugin]
            });
            const { output: [{ code }] } = await bundle.generate({
                exports: 'named'.format: 'cjs'
            });
            config = await loadConfigFromBundledFile(resolvedPath, code);
        }
        // normalize config root to absolute
        if(config.root && ! path_1.default.isAbsolute(config.root)) { config.root = path_1.default.resolve(path_1.default.dirname(resolvedPath), config.root); }// resolve plugins
        if (config.plugins) {
            for (const plugin ofconfig.plugins) { config = resolvePlugin(config, plugin); }}// load environment variables
        const env = loadEnv(mode, config.root || cwd);
        debug(`env: %O`, env);
        config.env = env;
        debug(`config resolved in The ${Date.now() - start}ms`);
        config.__path = resolvedPath;
        return config;
    }
    catch (e) {
        console.error(chalk_1.default.red(`[vite] failed to load config from ${resolvedPath}: `));
        console.error(e);
        process.exit(1); }}exports.resolveConfig = resolveConfig;
Copy the code

As we can see from the source code implementation, the parsing of the vite configuration will first read the vite command line to see if there is a configuration file. If so, the configuration is used; otherwise, the vite.config.js file is read from the current directory as the configuration information.

If it is declared locally, vite will first introduce the module through the CommonJS specification. But since we are writing in the ESM specification in vite.config.js, the errors introduced for this module will be caught here.

However, Vite handles non-CommonJS specification cases, internally using the Rollup tool to compile the vite.config.js configuration file into a format that commonJS can import. The specific implementation is as follows

async function loadConfigFromBundledFile(fileName, bundledCode) {
    const extension = path_1.default.extname(fileName);
    const defaultLoader = require.extensions[extension];
    require.extensions[extension] = (module, filename) = > {
        if (filename === fileName) {
            ;
            module._compile(bundledCode, filename);
        }
        else {
            defaultLoader(module, filename); }};delete require.cache[fileName];
    const raw = require(fileName);
    const config = raw.__esModule ? raw.default : raw;
    require.extensions[extension] = defaultLoader;
    return config;
}
Copy the code

This is the end of the parsing for config, so let’s go back to the main execution process in cli.js. After getting the parsed config, the file in the corresponding directory will be executed according to the command. We execute the build command, so the following logic will be entered

node_modules/vite/dist/cli.js

async function runBuild(options) {
    try {
        await require('./build').build(options);
        process.exit(0);
    }
    catch (err) {
        console.error(chalk_1.default.red(`[vite] Build errored out.`));
        console.error(err);
        process.exit(1); }}Copy the code

Build implementation

The complete core logic of build is as follows

/** * Bundles the app for production. * Returns a Promise containing the build result. */
async function build(options) {
    const { root = process.cwd(), base = '/', outDir = path_1.default.resolve(root, 'dist'), assetsDir = '_assets', assetsInlineLimit = 4096, cssCodeSplit = true, alias = {}, resolvers = [], rollupInputOptions = {}, rollupOutputOptions = {}, emitIndex = true, emitAssets = true, write = true, minify = true, silent = false, sourcemap = false, shouldPreload = null, env = {}, mode } = options;
    const isTest = process.env.NODE_ENV === 'test';
    process.env.NODE_ENV = mode;
    const start = Date.now();
    let spinner;
    const msg = 'Building for production... ';
    if(! silent) {if (process.env.DEBUG || isTest) {
            console.log(msg);
        }
        else {
            spinner = require('ora')(msg + '\n').start(); }}const indexPath = path_1.default.resolve(root, 'index.html');
    const publicBasePath = base.replace(/ [^ /]) $/.'$1 /'); // ensure ending slash
    const resolvedAssetsPath = path_1.default.join(outDir, assetsDir);
    const resolver = resolver_1.createResolver(root, resolvers, alias);
    const { htmlPlugin, renderIndex } = await buildPluginHtml_1.createBuildHtmlPlugin(root, indexPath, publicBasePath, assetsDir, assetsInlineLimit, resolver, shouldPreload);
    const basePlugins = await createBaseRollupPlugins(root, resolver, options);
    env.NODE_ENV = mode;
    const envReplacements = Object.keys(env).reduce((replacements, key) = > {
        replacements[`process.env.${key}`] = JSON.stringify(env[key]);
        return replacements;
    }, {});
    // lazy require rollup so that we don't load it when only using the dev server
    // importing it just for the types
    const rollup = require('rollup').rollup;
    const bundle = await rollup({
        input: path_1.default.resolve(root, 'index.html'),
        preserveEntrySignatures: false.treeshake: { moduleSideEffects: 'no-external' },
        onwarn: exports.onRollupWarning, ... rollupInputOptions,plugins: [
            ...basePlugins,
            ...
        ]
    });
    const { output } = await bundle.generate({
        format: 'es',
        sourcemap,
        entryFileNames: `[name].[hash].js`.chunkFileNames: `[name].[hash].js`. rollupOutputOptions }); spinner && spinner.stop();const cssFileName = output.find((a) = > a.type === 'asset' && a.fileName.endsWith('.css')).fileName;
    const indexHtml = emitIndex ? renderIndex(output, cssFileName) : ' ';
    if (write) {
        const cwd = process.cwd();
        const writeFile = async (filepath, content, type) => {
            await fs_extra_1.default.ensureDir(path_1.default.dirname(filepath));
            await fs_extra_1.default.writeFile(filepath, content);
            if(! silent) {console.log(`${chalk_1.default.gray(`[write]`)} ${writeColors[type](path_1.default.relative(cwd, filepath))} ${(content.length / 1024).toFixed(2)}kb, brotli: ${(require('brotli-size').sync(content) / 1024).toFixed(2)}kb`); }};await fs_extra_1.default.remove(outDir);
        await fs_extra_1.default.ensureDir(outDir);
        // write js chunks and assets
        for (const chunk of output) {
            if (chunk.type === 'chunk') {
                // write chunk
                const filepath = path_1.default.join(resolvedAssetsPath, chunk.fileName);
                let code = chunk.code;
                if (chunk.map) {
                    code += `\n//# sourceMappingURL=${path_1.default.basename(filepath)}.map`;
                }
                await writeFile(filepath, code, 0 /* JS */);
                if (chunk.map) {
                    await writeFile(filepath + '.map', chunk.map.toString(), 4 /* SOURCE_MAP */); }}else if (emitAssets) {
                // write asset
                const filepath = path_1.default.join(resolvedAssetsPath, chunk.fileName);
                await writeFile(filepath, chunk.source, chunk.fileName.endsWith('.css')?1 /* CSS */ : 2 /* ASSET */); }}// write html
        if (indexHtml && emitIndex) {
            await writeFile(path_1.default.join(outDir, 'index.html'), indexHtml, 3 /* HTML */);
        }
        // copy over /public if it exists
        if (emitAssets) {
            const publicDir = path_1.default.resolve(root, 'public');
            if (fs_extra_1.default.existsSync(publicDir)) {
                for (const file of await fs_extra_1.default.readdir(publicDir)) {
                    awaitfs_extra_1.default.copy(path_1.default.join(publicDir, file), path_1.default.resolve(outDir, file)); }}}}if(! silent) {console.log(`Build completed in The ${((Date.now() - start) / 1000).toFixed(2)}s.\n`);
    }
    // stop the esbuild service after each build
    esbuildService_1.stopService();
    return {
        assets: output,
        html: indexHtml
    };
}
exports.build = build;
Copy the code

As you can see, the content is still compiled using rollup, and the compiled configuration is the config object you just parsed, and the output is a static file in the dist directory.

There is no logic to perform function execution on the configuration in the vite.config.js file, so we can conclude that at least in the logic we have seen so far, there is no support for dynamic configuration of vite.config.js by exporting functions.

Ask for help

So where do we go for help at a time like this. The first thought was to Google the Internet and see if anyone else had seen a similar situation.

The only answer to this question is the vite official document and vite issues, and someone in StackOverflow asked you how to set the root directory of your project.

Let’s start with this question in StackOverflow, which is not quite the same as ours, not dynamic configuration, but why static configuration does not work.

Therefore, if there is a similar question in vite’s issues, I found that the search also failed.

He hesitated for a moment…

transit

I wondered if the version of Vite we were using really didn’t support dynamic configuration at all. So I went to vite’s Github home page to take a look at the latest version of Vite

V2.4.4!!!!!!

Take a look at the vite version of our project

"devDependencies": {
    "vite": "^ 0.16.6"
}
Copy the code

0.16.6 ~ ~ ~

Ah, I see.

Use NPM Info vite to check the version information of the Vite package

| [email protected] | MIT | deps: 4 versions: 215 Native-ESM powered web dev build tool https://github.com/vitejs/vite/tree/main/#readme bin: vite dist .tarball: https://registry.npmjs.org/vite/-/vite-2.4.4.tgz shasum: 8 c402a07ad45f168f6eb5428bead38f3e4363e47. Integrity: sha512-m1wK6pFJKmaYA6AeZIUXyiAgUAAJzVXhIMYCdZUpCaFMGps0v0IlNJtbmPvkUhVEyautalajmnW5X6NboUPsnw== .unpackedSize: 17.9 MB Dependencies: esbuild: ^0.12.8 postCSS: ^8.3.6 resolve: ^1.20.0 maintainers: - yyx990803 <[email protected]> - antfu <[email protected]> - patak <[email protected]> dist-tags: Beta: 2.5.0 - beta. 1 latest: 2.4.4Copy the code

The latest version is indeed v2.4.4, so far it has been largely uncovered.

update

Run yarn add [email protected] to install the vite package of the latest version and run yarn build again

You think this is the end of it? I thought so too at first, Emmmm

An error again

The following error is reported after the build of the vite version is updated

Yarn Run v1.22.10 $vite build vite V2.4.4 Building for Production... ✓ 2 Modules transformed. [rollup-plugin-dynamic import-variables] Unexpected token (1:0) file: /Users/wangxin/code/temp/github_cleaner/App.vue:1:0 error during build: SyntaxError: Unexpected token (1:0) at Parser.pp$4.raise (/Users/wangxin/code/temp/github_cleaner/node_modules/rollup/dist/shared/rollup.js:16965:13) at Parser.pp.unexpected (/Users/wangxin/code/temp/github_cleaner/node_modules/rollup/dist/shared/rollup.js:14473:8) at Parser.pp$3.parseExprAtom  (/Users/wangxin/code/temp/github_cleaner/node_modules/rollup/dist/shared/rollup.js:16342:10) at Parser.pp$3.parseExprSubscripts (/Users/wangxin/code/temp/github_cleaner/node_modules/rollup/dist/shared/rollup.js:16137:19) at Parser.pp$3.parseMaybeUnary (/Users/wangxin/code/temp/github_cleaner/node_modules/rollup/dist/shared/rollup.js:16103:17)  at Parser.pp$3.parseExprOps (/Users/wangxin/code/temp/github_cleaner/node_modules/rollup/dist/shared/rollup.js:16036:19) at Parser.pp$3.parseMaybeConditional (/Users/wangxin/code/temp/github_cleaner/node_modules/rollup/dist/shared/rollup.js:16019:19) at Parser.pp$3.parseMaybeAssign (/Users/wangxin/code/temp/github_cleaner/node_modules/rollup/dist/shared/rollup.js:15987:19) at Parser.pp$3.parseExpression (/Users/wangxin/code/temp/github_cleaner/node_modules/rollup/dist/shared/rollup.js:15951:19)  at Parser.pp$1.parseStatement (/Users/wangxin/code/temp/github_cleaner/node_modules/rollup/dist/shared/rollup.js:14663:45) error Command failed with exit code 1. info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.Copy the code

No problem is displayed. Run yarn dev to check the situation in development mode. At first, no problem occurs

I have to say, there is a React flavor. Maybe this way of reporting errors in the browser is to cater to React users?

Take a look at the console error message

Yarn run v1.22.10 $vite vite v2.4.4 dev server running at: > Local: http://localhost:3000/ > Network: use `--host` to expose ready in 310ms. 11:39:53 PM [vite] Internal server error: Failed to parse source for import analysis because the content contains invalid JS syntax. Install @vitejs/plugin-vue to  handle .vue files. Plugin: vite:import-analysis File: /Users/wangxin/code/temp/github_cleaner/App.vue 3 | <img alt="Vue logo" src="./logo.png" /> 4 | <HelloWorld msg="Hello 5 | Vue 3.0 + Vite "/ > < / div > | | ^ 6 < / template > 7 | at formatError (/Users/wangxin/code/temp/github_cleaner/node_modules/vite/dist/node/chunks/dep-c1a9de64.js:50738:46) at TransformContext.error (/Users/wangxin/code/temp/github_cleaner/node_modules/vite/dist/node/chunks/dep-c1a9de64.js:50734:19) at TransformContext.transform (/Users/wangxin/code/temp/github_cleaner/node_modules/vite/dist/node/chunks/dep-c1a9de64.js:74194:22) at async Object.transform (/Users/wangxin/code/temp/github_cleaner/node_modules/vite/dist/node/chunks/dep-c1a9de64.js:50939:30) at async transformRequest (/Users/wangxin/code/temp/github_cleaner/node_modules/vite/dist/node/chunks/dep-c1a9de64.js:66763:29) at async viteTransformMiddleware (/Users/wangxin/code/temp/github_cleaner/node_modules/vite/dist/node/chunks/dep-c1a9de64.js:66904:32)Copy the code

Vite does not know the syntax of the vue file when handling the module parsing.

The console also provides a solution, which is to install @vitejs/plugin-vue, a vite plug-in to parse.vue files.

Install the plugin manually

yarn add @vitejs/plugin-vue
Copy the code

After the project yarn dev is restarted, the error is still reported. It turns out that to install the plug-in, you also need to introduce and initialize the plug-in in ite. Config.js to let Vite know what rules to use to resolve.vue file syntax.

It’s a little naive to just install the plugin package and do nothing else, but that’s where vite’s plugin ecology comes in. Because Vite is designed not only for Vue but also for other frameworks such as React, there is no default support for Vue syntax. Instead, the special syntax for each framework is supported through plug-ins. This idea has been around since the more recent version of @vue/cli, if you look at vue’s ecology.

Over to

Instead of continuing to study the configuration of vite’s plug-in ecosystem, I looked at the official vite documentation, which has a different initial project method than vuE3: Yarn Create vite XXX — Template vue

There are more reasonable projects to initialize using the above command than the examples provided in the VUe3 documentation. The package.json dependencies are newer, and the default runtime command supports native preview in production mode. (This command is also not present when I run Vite Preview in a project that I initialized using vue3. This is also because the vite version of the template initialization project is too low.

package.json

{
  "name": "github_cleaner2"."version": "0.0.0"."scripts": {
    "dev": "vite"."build": "vite build"."serve": "vite preview"
  },
  "dependencies": {
    "vue": "^ 3.0.5"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^ 1.3.0"."@vue/compiler-sfc": "^ 3.0.5"."vite": "^ 2.4.4." "}}Copy the code

In addition, the project explicitly provides the configuration of vte.config.js by default and the configuration of vUE syntax supported by plug-ins, as follows:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()]
})
Copy the code

The end of the

At this point, we are finally able to conditionally configure a local run and compile build using vite+vue3, with output like this:

vite.config.js

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default ({ command, mode }) => {
  if (command === 'build') {
    return defineConfig({
      plugins: [vue()],
      base: '/github_cleaner/'})}return defineConfig({
    plugins: [vue()]
  })
}

Copy the code

dist/index.html

<! DOCTYPEhtml>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/github_cleaner/favicon.ico" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />
    <title>Vite App</title>
    <script type="module" crossorigin src="/github_cleaner/assets/index.2f5dd7dc.js"></script>
    <link rel="modulepreload" href="/github_cleaner/assets/vendor.e68e32f2.js">
    <link rel="stylesheet" href="/github_cleaner/assets/index.f498eb83.css">
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
Copy the code

analyse

There are a number of common problems encountered in exploring new technologies, so it is necessary to summarize this experience.

aboutVueecological

Vue’s author Evan You has mentioned for the past two years that his immediate focus will be on Vite, even more so than ve3

This information is important to know, which is why we used the example in the Vite documentation to initialize the project without a problem, whereas the vuE3 approach clearly had the issue of old versions.

aboutvite

When we search for the item condition config, not many valid search results appear. It is true that Vite is still in a very new state, and not enough problems have been encountered and thrown out by users.

So it is destined that the people who are using Vite today, the first ones, should be prepared to develop, practice and trial with Vite. This also means that it is not necessarily suitable for beginners, as you may not find effective solutions to your problems online.