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:
- perform
vite build
build
As acommand
Parameter isvite
Executable file parsing- Set inside the
mode
Property as in executionvite
Is not explicitly declared whenmode
, so usebuild
Command by defaultproduction
- With the
mode=production
Parameters to performconfig.js
In theresolveConfig
function
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.
aboutVue
ecological
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.