Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.
This article has participated in the “Digitalstar Project” and won a creative gift package to challenge the creative incentive money.
vision
It is hoped that this article will provide readers with an idea for storage/incremental project access to Vite, which will serve as a catalyst to reduce the cost of building capacity in this area
In the process of elaboration, we will gradually improve the tool webpack-viet-serve
Readers can fork the tool repository and customize the secondary development for individual/corporate project scenarios
background
Webpack is everywhere in today’s business development, and is the building tool used by most business projects.
As time goes on, existing projects get bigger and bigger, and development starts/builds take longer and longer. Optimization tools for Webpack are increasingly limited.
As a result, some scenarios have tools written in other languages to help build/develop improvements. Such as SWC (Rust), ESbuild (Go)
Of course, the above tool is not a complete build tool, can not replace webpack directly used, only through plugin, webpack work to improve the efficiency
Another popular solution is Bundleless, which makes use of the browser’s native ES Module support feature to let the browser take over the “packaging” work. The tool is only responsible for the corresponding conversion of resources requested by the browser, thus greatly reducing the startup time of the service and improving the development experience and happiness
Two of the best known products are Snowpack and Vite
The star of this article is Vite: the next generation front-end development and build tool
Since Vite is still under construction, it will take some time to completely replace WebPack. In order to ensure the stability of online projects, It is recommended that Vite be used as an optional capability during development.
# webpack devServer
npm run dev
# Vite devServer
npm run vite
Copy the code
The target
Provide the simplest Vite access solution for webPack project development environment
With minimal changes, in-app projects can enjoy the development benefits of Vite
plan
- Make a CLI tool that encapsulates Vite’s ability to start projects
- The webPack configuration is automatically converted to the Vite configuration by converging all the Vite related configurations within the plug-in
- Some optional parameters are provided to manually specify the location of the configuration file
Effect of the demo
Vue SPA
React SPA
In the simplest Demo projects, Vite’s startup /HMR speed is significantly faster than WebPack’s
Demos of other common project types will also gradually be refined into the source repository
implementation
1. Initialize the project
Complete engineering structure moved to warehouse
Register a start method
src/bin.ts
#! /usr/bin/env node
import { Command } from 'commander';
import { startCommand } from './command';
program.command('start')
.alias('s')
.action(startCommand);
program.parse(process.argv);
Copy the code
export default function startCommand() {
console.log('hello vite');
}
Copy the code
Add directives to package.json
- Among them
wvs
Is a custom instruction npm run dev
:typescript
Depending on the instructions provided, listen for file changes and automatically convert themjs
file
{
"bin": {
"wvs": "./dist/bin.js"
},
"scripts": {
"dev": "tsc -w -p ."."build": "rimraf dist && tsc -p ."}},Copy the code
The project root executes NPM link, registering instructions
npm link
Copy the code
test
wvs start
Copy the code
Then we used vuE-CLI and Create React App to Create two WEBpack SPA applications respectively for the following experiment
vue create vue-spa
Copy the code
npx create-react-app react-spa
Copy the code
2. The Vite is started
Vite startup is relatively simple, only need to execute the Vite command s
Start Vite using spawn in our CLI tool
- Among them
cwd
Use to specify the working directory of the child process - Stdio: Standard INPUT/output configuration for child processes
import { spawn } from 'child_process';
export default function startCommand() {
const viteService = spawn('vite'['--host'.'0.0.0.0'] and {cwd: process.cwd(),
stdio: 'inherit'}); viteService.on('close'.(code) = > {
process.exit(code);
});
}
Copy the code
Here for easy debugging, let’s install the global Vite
npm i -g vite
Copy the code
Add a
Hello Vite
to the startup template public/index.html
Run WVS Start in the demo project
Open corresponding address
# vue
http://localhost:3000/
# react
http://localhost:3001/
Copy the code
You get the following result, indicating that the page could not be found (unsurprisingly)
According to the documentation,Vite by default looks for index.html as the entry file for the project
This brings us to our first problem, which is the possibility of multiple template files in a multi-page application
How to dynamically specify the entry to this X. HTML based on the access route?
Before solving the problem, let’s simply refine the startup command by specifying a vite.config.js configuration file for it
With vite –help, you can see that the configuration file location is specified with the –config parameter
export default function startCommand() {
const configPath = require.resolve('. /.. /config/vite.js');
const viteService = spawn('vite'['--host'.'0.0.0.0'.'--config', configPath], {
cwd: process.cwd(),
stdio: 'inherit'}); }Copy the code
This is the absolute path to the configuration file
config/vite.ts
import { defineConfig } from 'vite';
module.exports = defineConfig({
plugins: [].optimizeDeps: {},});Copy the code
3. HTML template processing
The ability to extend Vite is to customize various plug-ins, according to the plug-in documentation
Write a simple plugin that uses the configServer hook to read resource requests made by the browser
import type { PluginOption } from 'vite';
export default function HtmlTemplatePlugin() :PluginOption {
return {
name: 'wvs-html-tpl'.apply: 'serve'.configureServer(server) {
const { middlewares: app } = server;
app.use(async (req, res, next) => {
const { url } = req;
console.log(url); next(); }); }}; }Copy the code
Introduced in the configuration file above
import { htmlTemplatePlugin } from '.. /plugins/index';
module.exports = defineConfig({
plugins: [
htmlTemplatePlugin(),
]
});
Copy the code
Start service observation again
- access
http://localhost:3000
, output in the terminal/
- access
http://localhost:3000/path1/path2
, output in the terminal/path1/path2
- access
http://localhost:3000/path1/path2?param1=123
, output in the terminal/path1/path2? param1=123
In devTools panel content as you can see, the first in the Accept field with a resource request head of text/HTML, such as application/XHTML + XML content, we will request in this field show that the HTML document
Modify the code that handles resource requests again
import { readFileSync } from 'fs';
import path from 'path';
import { URL } from 'url';
function loadHtmlContent(reqPath) {
// Single page defaults to public/index.html
const tplPath = 'public/index.html';
Path: reqPath
return readFileSync(path.resolve(process.cwd(), tplPath));
}
// Omit the previous code
app.use(async (req, res, next) => {
const { pathname } = new URL(req.url, `http://${req.headers.host}`);
const htmlAccepts = ['text/html'.'application/xhtml+xml'];
constisHtml = !! htmlAccepts.find((a) = > req.headers.accept.includes(a));
if (isHtml) {
const html = loadHtmlContent(pathname);
res.end(html);
return;
}
next();
});
Copy the code
Start the service again in Demo, and you’ll see Hello Vite correctly
An error will be found in the terminal
UnhandledPromiseRejectionWarning: URIError: URI malformed
Copy the code
Open the template and you’ll see that there is something else in there that contains variables that are handled by the HTmL-webpack-plugin in webpack
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>
<%= htmlWebpackPlugin.options.title %>
</title>
Copy the code
Here’s a simple method to do some simple things with the template (this method only deals with the current situation)
/** * Initializes the template content (replace <%= varName %> something) */
function initTpl(tplStr:string, data = {}, ops? :{ backup? :stringmatches? :RegExp[]}) {
const { backup = ' ', matches = [] } = ops || {};
// match %Name% <%Name%>
return [/
(. *)? /g].concat(matches).reduce((tpl, r) = > tpl.replace(r, (_, $1) = > {
const keys = $1.trim().split('. ');
const v = keys.reduce((pre, k) = > (pre instanceof Object ? pre[k] : pre), data);
return (v === null || v === undefined)? backup : v; }), tplStr); }Copy the code
If the template still has complex EJS syntax, you can use the EJS library for further processing
import ejs from 'ejs';
/** * ejs render */
function transformEjsTpl(html:string, data = {}) {
return ejs.render(html, data);
}
Copy the code
Of course, if there are other cases that are not considered, the template can be further processed according to the specific situation
Now integrate the methods you wrote above into your plug-in
export default function HtmlTemplatePlugin() :PluginOption {
return {
configureServer(server) {
const { middlewares: app } = server;
app.use(async (req, res, next) => {
// omit the code
if (isHtml) {
const originHtml = loadHtmlContent(pathname);
// Call the transformIndexHtml hook in the plug-in for further processing of the template
const html = await server.transformIndexHtml(req.url, originHtml, req.originalUrl);
res.end(html);
return;
}
next();
});
},
transformIndexHtml(html) {
// Data can be passed in some of the variables contained in the template
// You can get the WebPack configuration here to do the automatic conversion
return initTpl(html, {
PUBLIC_URL: '. '.BASE_URL: '/'.htmlWebpackPlugin: {
options: {
title: 'App',}}}); }}; }Copy the code
To this run again in the demo, the page ran, the terminal also no error, the page template to this is processed
Having the initial template means that we have provided entry to the page for Vite, but there are no js/ TS dependencies that are processed yet
Insert entry into the template
4. Specify an entry
Entrance to the filename (entryName) is usually (main | index), js | | ts JSX | TSX
- EntryBase in a single page application (SPA) is usually:
src
- EntryBase in multi-page applications (MPA) is usually:
src/pages/${pageName}
export default function pageEntryPlugin() :PluginOption {
return {
name: 'wvs-page-entry'.apply: 'serve'.transformIndexHtml(html, ctx) {
return html.replace('</body>'.`<script type="module" src="${getPageEntry(ctx.originalUrl)}"></script>
</body>
`); }}; }Copy the code
Take SPA as an example
function getPageEntry(reqUrl) {
// SPA
const SPABase = 'src';
return getEntryFullPath(SPABase);
}
Copy the code
GetEntryFullPath is implemented as follows
- Check whether the directory exists
- Read directories and traverse files using re
/(index|main)\.[jt]sx? $/
Determines whether a file is a target file
const resolved = (. p) = >path.resolve(getCWD(), ... p);const getEntryFullPath = (dirPath) = > {
if(! existsSync(resolved(dirPath))) {return false;
}
// main|index.js|ts|jsx|tsx
const entryName = /(index|main)\.[jt]sx? $/;
const entryNames = readdirSync(resolved(dirPath), { withFileTypes: true })
.filter((v) = > {
entryName.lastIndex = 0;
return v.isFile() && entryName.test(v.name);
});
return entryNames.length > 0 ? path.join(dirPath, entryNames[0].name) : false;
};
Copy the code
Add this plug-in to the configuration
import { pageEntryPlugin } from '.. /plugins/index';
module.exports = defineConfig({
plugins: [
pageEntryPlugin(),
]
});
Copy the code
Launched demo to see the effect, threw a bunch of errors
wvs start
Copy the code
The following is the framework-specific processing
React
- React: the content contains invalid JS syntax
React: JSX: JSX: JSX: JSX: JSX: JSX
- Uncaught ReferenceError: React is not defined
Install react at the top of the React component, or install @vitejs/plugin-react
import React from 'react';
Copy the code
- HMR support
Introduce the @vitejs/plugin-react plugin
import react from '@vitejs/plugin-react'
module.exports = defineConfig({
plugins: [
react(),
]
});
Copy the code
Vue
Need to add plug-in to handle.vue files
Introduce the @vitejs/plugin-vue plugin
import vue from '@vitejs/plugin-vue'
module.exports = defineConfig({
plugins: [
vue(),
]
});
Copy the code
While @vitejs/plugin-vue needs vue (>=3.2.13)
Because the previous use is NPM link to create soft connection for debugging, configuration files will be in the development directory to find Vue dependencies, not in the command run directory to find, will continue to throw the above problems
Here we install our dependencies locally in the Demo project and add the relevant directives in package.json
yarn add file:webpack-vite-service-workspace-path
Copy the code
{
"scripts": {
"vite": "wvs start -f vue"}},Copy the code
There are no React dependencies in the Vue project, so the @vitejs/plugin-react plugin cannot be introduced in the Vue project
You can add parameters related to the framework in the command entry to judge the processing, and only introduce plug-ins corresponding to the framework
// src/bin.ts
program.command('start')
.option('-f, --framework <type>'.'set project type [vue/react]')
.action(startCommand);
// src/command/start.ts
export default function startCommand(options:{[key:string] :string}) {
const { framework = ' ' } = options;
process.env.framework = framework.toUpperCase();
}
// src/config/vite.ts
import react from '@vitejs/plugin-react';
import vue from '@vitejs/plugin-vue';
const extraPlugins: any[] = [
process.env.framework === 'REACT' ? [react()] : [],
process.env.framework === 'VUE' ? [vue()] : [],
];
module.exports = defineConfig({
plugins: [
htmlTemplatePlugin(),
pageEntryPlugin(),
...extraPlugins,
],
});
Copy the code
That completes the two most important steps
5. Other engineering skills
There are already a number of plug-ins and solutions available in the community for common webPack capabilities, so this is a brief introduction
Of course, some of these plugins may not be able to handle the situation, or expect developers, dare to experiment, and then submit PR/issues to the plugin author
- Sass/Less: Installed in a dependency
Sass/Less
Can be - Component libraries are imported on demand: viet-plugin-style-import
- Process. Env: vite – plugin – env – compatible
- Xx /xx undefined: used
transformIndexHtml
Hook development plugins that introduce this method ahead of time in the templatepolyfill
Or the bottom line - .
conclusion
Enterprises: Most of them have their own research and development framework, and only need to add a CLI command for Vite startup to the research and development framework, so as to minimize the impact on the access party and the use cost
Personal: if you want to change the original code, you can follow the above process and develop your new project directly using the official Vite template
Bottom line: It’s nice to use Vite in development
Due to the limited space and time, part of the article only introduces the implementation ideas, and did not paste the complete code, the complete code can be viewed in the source repository, can also be forked directly for secondary development
The transformation of WebPack to Vite configuration will be covered in the next installment