WebPack Project Access to the General Scheme of Vite (below)

Vision

I hope that through this series of articles, you can provide a deposit / incremental project to access the idea of ​​Vite.

It will gradually improve during the elaboration process.webpack-vite-serveThis tool

Readers can directly form the secondary development of the personal / company project scene for personal / company project scenes

Foreword

Previous articleIn the middle, the process of handling the WebPack project access Vite is generally the following steps:

The processing of these contents can be passedVite pluginImplement

WebPack-Vite-Serve Introduction

During this time, the function of this library is continuously improved. Let’s first introduce it, and then explain some of the implementation principles of some plugins.

Target:The ability to access the Vite for the WebPack project

Installation

npm install webpack-vite-serve -D
# or
yarn add webpack-vite-serve -D
# or
pnpm add webpack-vite-serve -D

Add start command

# devServer
wvs start [options]
# build
wvs build [options]

Optional parameters

  • -f,--framework <type>: Specified business framework (Vue, React), automatically introduce the basic plugin related to the business framework
  • -s,--spa: According to the single page application directory structuresrc/${entryJs}
  • -m,--mpa::00src/pages/${entryName}/${entryJs}
  • -d,--debug [feat]: Print Debug Information
  • -w,--wp2vite: Usewp2viteAutomatically convert Webpack files

Other instructions

The project follows the directory structure of the conventional single page / multi-page application project.

Vite configuration through the officialvite.config.[tj]sProfile expansion

Effect

图片

Online experience DEMO address: createdstackblitz

If the network is unable to access, the Clone warehouse can be accessed in the Demo experience.

MPA support

dev-Page Template

The first isdevServerEnvironment page template processing

Get according to the request pathentryName

  • Use/Split request pathpaths
  • ​​Traversing to find the first onesrc/pages/${path}pathThis 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 || '';
}

Find template files, explore in order of order

  • src/pages/${entryName}/${entryName}.html
  • src/pages/${entryName}/index.html
  • public/${entryName}.html
  • public/index.html
Function LoadhtmlContent (REQPATH: STRING) {// Poofer page const page = [path.resolve (__ dirname, '../../public/index.html')]; // single page / multi-page default public / Index.html pages.unshift ('public / index.html')); // multi-page application can be further determined according to 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 (`SRC / PAGES / $ {EntryName} / $ {entryName} .html`);}} => EXSTSSYNC (V)); Return ReadFileSync (Page, {Encoding: ' UTF-8 '});

Dev-entryJs

Multi-page applicationentryJsRead according to the agreementsrc/pages/${entryName}/${main|index}Document

function getPageEntry (reqUrl) {if (isMPA ()) {const entryName = getEntryName (reqUrl); return !! entryName && getEntryFullPath ( `src / pages / $ {entryName}`);} // default SPA const SPABase = 'src'; return gelectryfullpath (spabase);

Build

The entrance to Vite ishtmlTemplate, you can passbuild.rollup.inputProperty settings

// 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',
      },
    },
  },
});

Follow the above configuration, the HTML directory in the product will be as follows.

* dist
  * src/pages/index/index.html
  * src/pages/second/second.html
  * assets

Not very conforming to the usual habits, the regular format is as follows

* dist
  * index.html
  * second.html
  * assets

So need to pass through pluginsProcessing Building Inlet DocumentsandAdjust the product position after the construction

Plug-in structure

export default function BuildPlugin (): PluginOption {let userConfig: ResolvedConfig = null; return {name: 'wvs-build', // effect only apply during the construction phase: 'build', // Get the final configuration configResolved (cfg) {UserConfig = CFG;}, // Plug-in configuration process config () {}, resolveid (ID) {}, load (id) {}, // is completed after closebundle () {},};

PassconfigResolvedThe hook acquires the final configuration and the configuration is provided to other hooks.

Get Entry

First getsrc/pagesAll Entry

const entry = []; if (ISMPA ()) {entry.push (... getmpaectry ());} else {// single page application entry.push ({entryName: 'index', entryhtml: 'public /index.html ', entryjs: getentryfullpath (' src '),});

ENTRY is defined as

interface Entry{
  entryHtml:string
  entryName:string
  entryJs:string
}

The acquisition logic is as follows

  • Get allEntryName
  • Get each entry to obtain each entryentryJsentryHtml
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,
      };
    });
}

Generate build configuration

Based onentryGeneratebuild.rollup.input

  • Get eachentryHtmlThe content is then usedmapTemporary storage
  • Build an entry template pathhtmlEntryPathTakeentryJsCatalogindex.html

In facthtmlEntryPathThis path does not have any files

So you need to pass other hooks, usehtmlContentMapStorage content for further processing

const htmlcontentmap = new map (); / / omitted other independent 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); // stored content htmlContentMap.set (htmlEntryPath, html); pre [entryName] = htmlentrypath; return pre;}, {}); return {build: {rollupoptions: {INPUT,},},};

Build an entry content generation

resolveIdloadHook complete the handling of entrance documents

  • idThe path to the resource request
  • htmlContentMapRemove 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;
  },
}

Product catalog adjustment

UsecloseBundleHook, after the service is closed, the service is adjusted before the service is closed.

  • Traverseentrywilldist/src/pages/entryName/index.htmlMove todist
  • Removedist/src
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 Resolved RMDIRSYNC (RESOLVED (Outdir, 'SRC'), { Recursive: true};

WebPack configuration conversion

The current community has a CLI tool:wp2viteSupport this feature, so the author does not intend to build one from 0-1

Because it is a CLI tool, no some direct calls are provided to get the configuration before and after the conversion, soThe experience of the access plug-in is not very good, follow-up to mention PR transformation of this tool

Plugins that access WP2VITE are achieved

IMPORT WP2VITE FROM 'WP2VITE'; / / omitting IMPORT 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'); // Judgment if there is vite.config.js, index.html // avoid WP2Vite override FILES.FOREACH ((f) => {if (EXISSSSSSSSSSSSSSSSSYNC (f) )) {ContentMap.Set (f, readfilesync (f, {encoding: 'UTF-8'}));}}); // Convert the configuration file vite.config.js await wp2vite.start (getcwd (), { Force: false, // Uniform Open Debug debug: !! process.env.debug,}); // Todo: PR Optimization / / Conversion time calculation console.timeEnd ('wp2Vite'); // Get WP2VITE conversion Configuration const cfg = await getUserConfig (ENV, 'JS'); ContentMap.Foreach ((v, k) => {if (v) { // If you modify the content, restore content WriteFileSync (k, v);} else {// Remove the created file unlinkSync (k);}}); if (cfg.config) {const {config} = cfg || {}; // Remove the required configuration Return {Resolve: config? .Resol, Server: config ?server, css: config? .Css,};} return null;};

Wp2Vite, an external exposurestartMethod call

After the call, 2 new files will be generated according to the WebPack configuration of the project.vite.config.js,index.html) and modifypackage.jsonAdd instructions and dependence

So if there is these files in the project before generating, you need to store these content first.

Among them, users are configured.getUserConfigImplementation

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);
}

Vite providedloadConfigFromFileMethod, only need to be used in this method to do a layer of simple encapsulation, and the method is automatically converted to the TS syntax in the method.

Summary

So far, the capacity of construction has basically met the development of conventional projects.

The user does not have the user to add directly to the project.viteThe configuration file is expanded.

Follow-up plan

  1. Currentlywp2viteIn configuring this piece, it is not too satisfied with the use requirements, ready to mention PR Enhancements
  2. Pumgment internal capabilities into a separate Vite plugin