“This is the first day of my participation in the Gwen Challenge in November. Check out the details: The last Gwen Challenge in 2021”
vision
It is hoped that this series of articles will provide readers with an idea for storage/incremental project access to Vite, thereby reducing 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
preface
In the last installment, I outlined the following steps for connecting webPack projects to Vite:
- HTML Template processing
- EntryJs processing
- Webpack configuration is migrated to vite configuration
- Vite plugin to replace the WebPack plugin
The processing of these contents can be achieved through the Vite plug-in
Webpack – vite – serve is introduced
During this period of time, we are constantly improving the functions of this library. Here is a brief introduction to its use, and then explain the implementation principle of some plug-ins
Goal: To provide webPack projects with one-click access to Vite
Install dependencies
npm install webpack-vite-serve -D
# or
yarn add webpack-vite-serve -D
# or
pnpm add webpack-vite-serve -D
Copy the code
Add startup instructions
# devServer
wvs start [options]
# build
wvs build [options]
Copy the code
Optional parameters
-f,--framework <type>
: Specifies the service framework (VUE, React) to be used. Basic plug-ins related to the service framework are automatically introduced-s,--spa
: The directory is processed according to the single-page application directory structuresrc/${entryJs}
-m,--mpa
: The directory is processed according to the multi-page application directory structuresrc/pages/${entryName}/${entryJs}
-d,--debug [feat]
: Displays debugging information-w,--wp2vite
Use:wp2viteAutomatically convert Webpack files
Other instructions
The project follows the normal single page/multi-page application project directory structure
Vite configuration can be extended through the official vite. Config.[tj]s configuration file
The effect
Online demo address: Stackblitz has been created
If the demo cannot be accessed due to network problems, clone the repository to experience demo
MPA support
Dev- Page template
The first is page template processing in the devServer environment
Obtain entryName based on the request path
- use
/
Split the request path to getpaths
- Go through and find the first one
src/pages/${path}
The existence ofpath
, path isentryName
function getEntryName(reqUrl:string, cfg? :any) {
const { pathname } = new URL(reqUrl, 'http://localhost');
const paths = pathname.split('/').filter((v) = >!!!!! v);const entryName = paths.find((p) = > existsSync(path.join(getCWD(), 'src/pages', p)));
if(! entryName) {console.log(pathname, 'not match any entry');
}
return entryName || ' ';
}
Copy the code
Find the template files in the following order
src/pages/${entryName}/${entryName}.html
src/pages/${entryName}/index.html
public/${entryName}.html
public/index.html
function loadHtmlContent(reqPath:string) {
// The bottom page
const pages = [path.resolve(__dirname, '.. /.. /public/index.html')];
// Single page/multiple pages default public/index.html
pages.unshift(resolved('public/index.html'));
// Multi-page applications can make further judgments based on the requested path
if (isMPA()) {
const entryName = getEntryName(reqPath);
if (entryName) {
pages.unshift(resolved(`public/${entryName}.html`));
pages.unshift(resolved(`src/pages/${entryName}/index.html`));
pages.unshift(resolved(`src/pages/${entryName}/${entryName}.html`)); }}const page = pages.find((v) = > existsSync(v));
return readFileSync(page, { encoding: 'utf-8' });
}
Copy the code
Dev-entryJs
Multi-page application entryJs is read by the agreement SRC/pages / ${entryName} / ${main | index} file
function getPageEntry(reqUrl) {
if (isMPA()) {
const entryName = getEntryName(reqUrl);
return!!!!! entryName && getEntryFullPath(`src/pages/${entryName}`);
}
/ / the default SPA
const SPABase = 'src';
return getEntryFullPath(SPABase);
}
Copy the code
Build
The entry point to a Vite build is an HTML template, which can be set via the build.rollup.input property
// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
build: {
rollupOptions: {
input: {
index: 'src/pages/index/index.html'.second: 'src/pages/second/second.html',},},},});Copy the code
As configured above, the HTML directory in the build product would look like this
* dist
* src/pages/index/index.html
* src/pages/second/second.html
* assets
Copy the code
Not quite in accordance with the usual convention, the general format is as follows
* dist
* index.html
* second.html
* assets
Copy the code
The plugin handles the build entry file and adjusts the location of the artifacts after the build
The plug-in structure
export default function BuildPlugin() :PluginOption {
let userConfig:ResolvedConfig = null;
return {
name: 'wvs-build'.// Only in the construction phase
apply: 'build'.// Get the final configuration
configResolved(cfg) {
userConfig = cfg;
},
// Plug-in configuration processing
config(){},resolveId(id){},load(id){},// After the build is complete
closeBundle(){}}; }Copy the code
The configResolved hook gets the final configuration, which is made available to other hooks
Gain entry
First get all the entries under SRC/Pages
const entry = [];
if(isMPA()) { entry.push(... getMpaEntry()); }else {
// Single page application
entry.push({
entryName: 'index'.entryHtml: 'public/index.html'.entryJs: getEntryFullPath('src')}); }Copy the code
Entry is defined as
interface Entry{
entryHtml:string
entryName:string
entryJs:string
}
Copy the code
The acquisition logic is as follows
- So let’s get all of them
EntryName
- In traversal to obtain the corresponding of each entry
entryJs
withentryHtml
export function getMpaEntry(baseDir = 'src/pages') {
const entryNameList = readdirSync(resolved(baseDir), { withFileTypes: true })
.filter((v) = > v.isDirectory())
.map((v) = > v.name);
return entryNameList
.map((entryName) = > ({ entryName, entryHtml: ' '.entryJs: getEntryFullPath(path.join(baseDir, entryName)) }))
.filter((v) = >!!!!! v.entryJs) .map((v) = > {
const { entryName } = v;
const entryHtml = [
resolved(`src/pages/${entryName}/${entryName}.html`),
resolved(`src/pages/${entryName}/index.html`),
resolved(`public/${entryName}.html`),
resolved('public/index.html'),
path.resolve(__dirname, '.. /.. /public/index.html'),
].find((html) = > existsSync(html));
return {
...v,
entryHtml,
};
});
}
Copy the code
Build build configuration
Build. Rollup. Input is generated from the resulting entry
- For each
entryHtml
The content is then usedmap
Make temporary storage - Build the entry template path
htmlEntryPath
takeentryJs
The directory to addindex.html
The htmlEntryPath path doesn’t actually have any files
So you need to further process the content stored in htmlContentMap through other hooks
const htmlContentMap = new Map(a);// omit other extraneous code
{
config() {
const input = entry.reduce((pre, v) = > {
const { entryName, entryHtml, entryJs } = v;
const html = getEntryHtml(resolved(entryHtml), path.join('/', entryJs));
const htmlEntryPath = resolved(path.parse(entryJs).dir, tempHtmlName);
// Store content
htmlContentMap.set(htmlEntryPath, html);
pre[entryName] = htmlEntryPath;
return pre;
}, {});
return {
build: {
rollupOptions: { input, }, }, }; }}Copy the code
Build entry content generation
The resolveId and the Load hook handle the import file
- Among them
id
Is the path of the resource request - And then directly from
htmlContentMap
Remove the contents of the template
{
load(id) {
if (id.endsWith('.html')) {
return htmlContentMap.get(id);
}
return null;
},
resolveId(id) {
if (id.endsWith('.html')) {
return id;
}
return null; }},Copy the code
Product catalog adjustment
Use the closeBundle hook to adjust the file after the build is complete and before the service is closed
- traverse
entry
willdist/src/pages/entryName/index.html
Move to thedist
Under the - remove
dist/src
The content of the
closeBundle() {
const { outDir } = userConfig.build;
// Directory adjustment
entry.forEach((e) = > {
const { entryName, entryJs } = e;
const outputHtmlPath = resolved(outDir, path.parse(entryJs).dir, tempHtmlName);
writeFileSync(resolved(outDir, `${entryName}.html`), readFileSync(outputHtmlPath));
});
// Remove temporary resources
rmdirSync(resolved(outDir, 'src'), { recursive: true });
}
Copy the code
Webpack configures the transformation
There is currently one CLI tool in the community: Wp2vite supports this functionality, so I’m not going to build another from 0-1
Because it is a CLI tool, there is no direct method to call to obtain the configuration before and after the transformation, so the experience of using the access plug-in is not very good. We plan to improve the tool by PR in the future
The plug-in to access Wp2Vite is implemented as follows
import wp2vite from 'wp2vite';
// omit unimportant imports
export default function wp2vitePlugin() :PluginOption {
return {
name: 'wvs-wp2vite'.enforce: 'pre'.async config(_, env) {
const cfgFile = resolved('vite.config.js');
const tplFile = resolved('index.html');
const contentMap = new Map([[cfgFile, ' '], [tplFile, ' ']]);
const files = [cfgFile, tplFile];
console.time('wp2vite');
// Check whether vite. Config. js and index.html exist
Avoid wp2vite overwriting
files.forEach((f) = > {
if (existsSync(f)) {
contentMap.set(f, readFileSync(f, { encoding: 'utf-8'})); }});// Convert the configuration file vite.config.js
await wp2vite.start(getCWD(), {
force: false.// Enable the debug function
debug:!!!!! process.env.DEBUG, });// TODO:To optimize PR
// Conversion time calculation
console.timeEnd('wp2vite');
// Get the configuration converted from wp2vite
const cfg = await getUserConfig(env, 'js');
contentMap.forEach((v, k) = > {
if (v) {
// If the content is modified, restore the content
writeFileSync(k, v);
} else {
// Remove the created fileunlinkSync(k); }});if (cfg.config) {
const { config } = cfg || {};
// Leave the required configuration
return {
resolve: config? .resolve,server: config? .server,css: config? .css, }; }return null; }}; }Copy the code
Wp2vite exposes a start method call
After the call, two new files (viet.config. js, index.html) are generated according to the webpack configuration of the project, and package.json is modified to add directives and dependencies
Therefore, if these files exist in the project, they need to be stored before generation
The implementation of getUserConfig to obtain user configuration is as follows
import { loadConfigFromFile, ConfigEnv } from 'vite';
export function getUserConfig(configEnv:ConfigEnv, suffix = ' ') {
const configName = 'vite.config';
const _suffix = ['ts'.'js'.'mjs'.'cjs'];
if (suffix) {
_suffix.unshift(suffix);
}
const configFile = _suffix.map((s) = > `${configName}.${s}`).find((s) = > existsSync(s));
return loadConfigFromFile(configEnv, configFile);
}
Copy the code
Vite provides the loadConfigFromFile method, which only needs to do a simple layer of encapsulation in this method can be used directly. The method uses esbuild to automatically convert TS and ES syntax
conclusion
So far, the capacity built has been basically sufficient for the development of conventional projects
Users can directly add vite configuration files in the project for their own expansion
Subsequent planning
- At present
wp2vite
In the configuration transformation of this piece, is not enough to meet the requirements of use, ready to improve the PR - Separate the internal capabilities into separate Vite plug-ins and provide them externally