1. Webpack principle brief introduction

1.1 Core Concepts

JavaScript module Bundler. By analyzing the dependencies between modules, all modules are finally packaged into one or more bundlers, which can be directly referenced by HTML. In essence, Webpack simply provides packaging capabilities and a file-handling mechanism, and then precompiles and packages the code through the various Loaders and plugins in the ecosystem. Therefore, Webpack has a high degree of extensibility and can better play the power of community ecology.

  • Entry: Entry file from which Webpack will start analysis and compilation.
  • Output: export path. The file path and file name of the bundler file created after packaging.
  • Module: any file can be used as a Module in Webpack, and will be loaded and packaged according to different loaders configured;
  • Chunk: code blocks. You can combine all module codes into one or more code blocks based on configuration for loading on demand to improve performance.
  • Loader: module Loader for loading and converting various file types;
  • Plugin: Extension plug-in, through Webpack corresponding event hooks, can intervene in any link in the packaging process, so as to modify the code as needed;

1.2 Workflow (load – Compile – Output)

  1. Read configuration files, initialize configuration parameters according to commands, and create Compiler objects;
  2. Call the plug-in’s Apply method to mount the plug-in listener, and then compile from the entry file;
  3. According to the file type, the corresponding Loader is called to compile the module, and the corresponding event is triggered at the appropriate time point, and Plugin is called for execution. Finally, the dependent module is found according to the module dependency, and the third step is performed recursively.
  4. Wrap all compiled code into code blocks (Chuck) and determine the output by dependency and configuration. This step, the file can still be modified by Plugin;
  5. Finally, according to Output, the contents of the file are written to the specified folder one by one to complete the whole process;

1.3 Module Packaging

(function(modules) {// simulate require function, load module from memory; Function __webpack_require__(moduleId) {// Cache module if (installedModules[moduleId]) {return installedModules[moduleId].exports; } var module = installedModules[moduleId] = { i: moduleId, l: false, exports: {} }; // Execute code; modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); // Flag: indicates whether the loading is complete. module.l = true; return module.exports; } / /... // Start loading entry file; return __webpack_require__(__webpack_require__.s = "./src/index.js"); })({"./ SRC /index.js": function (module, __webpack_exports__, __webpack_require__) {// Execute compiled code using eval; // Continue recursively referencing module internal dependencies; // The template string is not used for readability purposes; eval(` __webpack_require__.r(__webpack_exports__); // var _test__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__("test", ./src/test.js"); `); }, "./src/test.js": function (module, __webpack_exports__, __webpack_require__) { // ... }})Copy the code

Conclusion:

  • Module mechanisms: WebPack itself implements a mechanism for simulating modules by wrapping them around business code to provide a module mechanism;
  • File compilation: WebPack provides a set of compilation rules that pipe file strings through loaders and plugins.

1.4 Packaging principle of WebPack

  • Initialization parameter: Reads and merges parameters from configuration files and Shell statements to arrive at the final parameters
  • Begin to compileInitialize the Compiler object with the parameters obtained in the previous step, load all the configured plug-ins, and execute the object’s run method to start compiling
  • Determine the entrance: Finds all entry files according to the entry in the configuration
  • Compile the module: Starting from the import file, call all configured Loader to translate the module, find the module that the module depends on, and then recurse this step until all import dependent files are processed in this step
  • Complete module compilation: After using Loader to translate all modules in Step 4, the final content of each module after translation and the dependencies between them are obtained
  • Output resources: Assemble chunks containing multiple modules one by one according to the dependency between the entry and modules, and convert each Chunk into a separate file and add it to the output list. This step is the last chance to modify the output content
  • Output complete: After determining the output content, determine the output path and file name based on the configuration, and write the file content to the file system

1.5 summarize

  1. Initialization parameters: Read and merge parameters from configuration files and Shell statements to arrive at the final configuration parameters.
  2. Start compiling: Initializes the parameters obtained from the previous stepCompilerObject, loads all configured plug-ins, executes the object’srunMethod starts compiling.
  3. Define entry: root scope according to configurationentryFind all the entry files.
  4. Compile module: from the entry file, call all configuredloaderTranslate the module and find the module that the module depends on. This step is performed recursively until all the module files of the entry dependency have been processed by this step.
  5. Complete module compilation: After using Loader to translate all modules in step 4, the final content of each module after translation and the dependencies between them are obtained.
  6. Output resources: Assembled into modules based on the dependencies between entry and modulechunkAnd then put eachchunkAdd it to the output list as a separate file. This step is the last chance to modify the output.
  7. Output complete: After determining the output content, determine the output path and file name based on the configuration, and write the file content to the file system.

2. Webpack hot update principle

  • When one or more files are modified;
  • The file system receives changes and notifies themwebpack;
  • webpackRecompile to build one or more modules and notify the HMR server for updates;
  • HMR ServerusewebSocketnoticeHMR runtimeNeed to update,HMRRun time passHTTPRequest updatejsonp
  • HMRThe runtime replaces the modules in the update and, if it is determined that they cannot be updated, triggers a full page refresh

3 webpack Loader

Since Webpack is based on Node, Webpack can only recognize JS modules, such as CSS/HTML/image files, and can’t load, so you need a converter for different formats. What Loader does is not hard to understand: it modifiers the strings passed in by Webpack as needed. For example, the simplest Loader:

Html-loader /index.js module.exports = function(htmlSource) {// return a string of processed code // remove all comments in the HTML file return htmlSource.replace(/<! --[\w\W]*? -->/g, '') }Copy the code

Of course, the actual Loader is not so simple. It usually needs to analyze the code, build an AST (abstract syntax tree), iterate for targeted modifications, and then regenerate the new code string. For example, babel-loader performs the following steps:

  • babylon 将 ES6/ES7Code parsing intoAST
  • babel-traverse 对 ASTErgodic translation is performed to obtain a new AST
  • newASTthroughbabel-generatorConverted toES5

Loader features:

  • Chain delivery, chain execution in reverse order when configured;
  • Based on the Node environment, it has higher permissions, such as adding, deleting, checking and modifying files;
  • Can be synchronous or asynchronous;

Commonly used Loader:

  • file-loader: Load file resources, such as fonts/pictures, with the function of moving/copying/naming.
  • url-loader: usually used to load images, you can convert small images directly to Date URLS, reducing requests;
  • babel-loader: Load JS/JSX files, convert ES6 / ES7 codes to ES5, and smooth out compatibility problems;
  • ts-loaderLoad ts/TSX files and compile TypeScript.
  • style-loader: Sets the CSS code to<style>Tags are inserted into the HTML;
  • css-loader: parse @import and URL (), referencing CSS files and corresponding resources;
  • postcss-loader: Used for CSS compatibility processing, with many functions, such as adding prefixes, unit conversion, etc.
  • less-loader / sass-loader: CSS preprocessor, in the CSS added a lot of syntax, improve the development efficiency;

Writing principles:

  • Single rule: Each Loader does only one thing;
  • Chain calls: Webpack calls each Loader in a chain order;
  • Unified principle: Follow Webpack design rules and structure, input and output are strings, each Loader is completely independent, plug and play;

4. webpack Plugin

The plug-in system is a key factor in the success of Webpack. Webpack triggers a number of event hooks throughout the life of the build, and the Plugin can listen for these events to make targeted changes to the packaged content at the appropriate point in time as required.

The simplest plugin looks like this:

Class Plugin{// The apply method is called when the plug-in is registered. // The apply method receives the Compiler object. // The Api provided by the Compiler can listen for events. // compilation is used to listen for each compilation loop // each file change, Compiler. Plugin ('compilation',function(compilation) {})}}Copy the code

Register plug-ins:

// webpack.config.js
module.export = {
	plugins:[
		new Plugin(options),
	]
}
Copy the code

Event flow mechanism:

Webpack is like an assembly line in a factory. Raw materials are processed one by one by Loader and Plugin, and finally output results.

  • Through the chain call, string up one Loader in sequence;
  • Plugins can be inserted at every step of the production process through an event flow mechanism;

At the heart of the Webpack event stream programming paradigm is the base class Tapable, which implements the subscription and broadcast of events in observer mode:

Const {SyncHook} = require("tapable") const hook = new SyncHook(['arg']) // subscribe to hook. Tap ('event', (arg) = > {/ / 'event - hook' console. The log (arg)}) / / radio hook. Call (' event - hook)Copy the code

The two most important classes in Webpack — Compiler and Compilation — inherit from Tapable and also have this event flow mechanism.

  • Compiler: It can be simply understood as the instance of Webpack, which contains all configuration information in the current Webpack, such as options, loaders, plugins and other information. It is globally unique and can only be initialized at startup and passed one by one with the life cycle.

  • Compilation: Can be called a Compilation instance. When listening for file changes, Webpack creates a new Compilation object and starts a new Compilation. It contains the current input resources, output resources, changing files, and so on. At the same time, through its API, it can listen to the event hooks triggered during the compilation process.

  • The difference between:

    • CompilerGlobally unique and exists from start to end;
    • CompilationEach compilation cycle is recreated for each compilation;
  • Commonly used the Plugin:

    • UglifyJsPlugin: Compress and obfuscate code;
    • CommonsChunkPlugin: Code split;
    • ProvidePlugin: automatic loading module;
    • Html-webpack-plugin: loads HTML files and introduces CSS/JS files;
    • Extract-text-webpack-plugin/mini-css-extract-plugin: Extract styles, generate CSS files; DefinePlugin: Define global variables;
    • Optimize – CSS -assets-webpack-plugin: OPTIMIZE – CSS -webpack-plugin;
    • Webpack-bundle-analyzer: Code analysis;
    • Compression -webpack-plugin: use gzip to compress JS and CSS
    • Happypack: Use multiple processes to speed up code building;
    • EnvironmentPlugin: Defines environment variables;
  • Call the plug-in apply function to pass in the Compiler object

  • The Compiler object listens for events

What is the difference between loader and plugin?

By default, WebAPck can only package JS and JOSN modules. To package other modules, loader needs to be used. Loader can convert the contents of the module into content that webPack or other LaoDer can recognize.

  • loaderModule conversion, or loader. Different files, different needsloaderTo deal with.
  • pluginPlugins can participate in the entire WebPack packaging process, and different plugins can do different events at the right time.

What plug-ins are in WebPack, and what do they do?

  • html-webpack-pluginAutomatically creates an HTML file and inserts the packaged JS into the HTML file
  • clean-webpack-pluginBefore each package, delete all contents in the entire output folder
  • mini-css-extrcat-pluginPull out the CSS code and put it in a separate file
  • optimize-css-assets-pluginCompress CSS

Implement a plug-in that compiles the exit command

apply (compiler) {
  const afterEmit = (compilation, cb) => {
    cb()
    setTimeout(function () {
      process.exit(0)
    }, 1000)
  }

  compiler.plugin('after-emit', afterEmit)
}
}

module.exports = BuildEndPlugin
Copy the code
<script> export default { mounted () { var isGithub = location.href.indexOf('FE-Interview-Questions')! ==-1 var sId = isGithub ? '59154049' : '66575297' var script = document.createElement("script"); script.type = "text/javascript" script.charset="UTF-8" script.src = `http://tajs.qq.com/stats?sId=${sId}` document.body.appendChild(script); } } </script>Copy the code

5. Webpack compilation optimization

Code optimization:

Dead code elimination is a process adopted by many programming languages. This process is called dead Code elimination (DCE).

For example, our UglifyJs will help us remove code that cannot be executed in production, such as:

var fn = function() { return 1; // The following code is impossible to execute; // Use UglifyJs (Webpack4+ built-in) to do DCE; var a = 1; return a; }Copy the code

Tree-shaking is a metaphor. We compare the packaged code to a tree, which actually means that the tool “shakes” our packaged JS code, and “shakes” the unused code down (delete). That is, eliminate module code that is referenced but not used.

  • How it works: Because it is optimized at compile time, the basic premise is static analysis of the syntax, and ES6’s modular mechanism provides this possibility. Without a runtime, the code can be literally statically analyzed to determine the corresponding dependencies.

  • Problem: Functions with side effects cannot be shaken by tree-shaking

    • In reference to some third-party libraries, it is necessary to observe whether the amount of code introduced is as expected;
    • Write pure function as far as possible to reduce the side effects of function;
    • You can usewebpack-deep-scope-plugin, scope analysis can be carried out to reduce the occurrence of such cases, but attention still needs to be paid;

Code-spliting: code splitting technology, which is used to divide code into multiple copies for lazy loading or asynchronous loading to avoid the large size of the first screen loading caused by packing the code into one copy.

  • SplitChunksPlugin is used in Webpack for splitting;

  • Split by page: package different pages into different files;

  • Split by function:

    • Large modules such as player and computing library are split and then lazily loaded and introduced;
    • Extracting reusable business code and reducing redundant code;
  • Split by file modification frequency: Package infrequently modified code, such as third-party libraries, separately without changing the hash value of the file to maximize the use of the browser cache.

In the case of scope promotion, the scattered modules are divided into the same scope, avoiding the repeated introduction of codes, and effectively reducing the packaged code volume and memory loss at runtime.

Compilation performance optimization:

  • Upgrading to the latest version of Webpack can effectively improve compilation performance;

  • Use dev-server/module hot replacement (HMR) to improve the development experience;

    • Ignoring node_modules can improve compilation efficiency when listening.
  • Narrow the compilation

    • modules: Specify module path to reduce recursive search;
    • mainFields: Specify entry file description field to reduce search;
    • noParse: Avoid loading of modular files;
    • includes/exclude: Specify search scope/exclude unnecessary search scope;
    • alias: Cache directories to avoid repeated addressing;
  • babel-loader

    • ignorenode_moudlesTo avoid compiling code that has already been compiled in third-party libraries
    • usecacheDirectory, can cache compilation results, avoid repeated compilation
  • Multiprocess concurrency

    • webpack-parallel-uglify-plugin: Multi-process concurrent compression of JS files, improve compression speed;
    • HappyPack: multi-process concurrent filesLoaderParsing;
  • Third-party library module cache:

    • DLLPlugin 和 DLLReferencePluginIt can be packaged and cached ahead of time to avoid recompiling every time;
  • Using the analysis

    • Webpack Analyse / webpack-bundle-analyzerAnalyze the packaged files to find areas to optimize
    • Configure profile: true to monitor the time spent in each compilation phase and find the most time spent
  • source-map:

    • Development:cheap-module-eval-source-map
    • Production:hidden-source-map;

Optimize webPack packaging speed

  • Reduce file search scope

    • Like through an alias
    • loader 的 test.include & exclude
  • Webpack4 compresses parallelism by default

  • Happypack is called concurrently

  • Babel can also be compiled by cache

6. Webpack import() principle

Principle of dynamic import

Import () method for dynamic loading

  • This feature allows us to load our code on demand and use itpromiseTo retrieve the loaded package
  • All are in the codeimport()The modules will all be grouped into a separate package and placed inchunkStore in the directory. When the browser runs to this line of code, it automatically requests the resource, enabling asynchronous loading
// Here is a simple demo. // As you can see, the syntax of import() is very simple. Import ('lodash'). Then (_ => {// Do something with lodash (A.K.A '_')... })Copy the code

How to implement dynamic import in Webpack?

  1. useimport(/** webpackChunkName: "lodash" **/ 'lodash').then(_ => {})At the same timewebpack.config.jsTo configureThe output of chunkFilenamefor[name].bunld.jsThe module to be imported is pulled separately into onebundleTo achieve code separation.
  2. useasyncBecause ofimport()It returns onepromiseSo we can useasyncDelta function to simplify it, but you need tobabelSuch preprocessor and processing conversionasyncThe plug-in.const _ = await import(/* webpackChunkName: "lodash" */ 'lodash');

7. What kinds of file fingerprints does Webpack have?

  • hashIs related to the construction of the entire project, as long as there are file changes in the project, the entire project is builthashValues change, and all files share the samehashValue. (Granularity of the entire project)
  • chunkhashIs based on the different entry for dependency file parsing, build the correspondingchunk(module), generate the correspondinghashValue. Only the modified oneschunkNew ones are generated only after being rebuilthashValue, doesn’t affect anything elsechunk. (particle sizeentryFor each entry file)
  • contenthashIt has to do with each generated file, each file has a uniquehashValue. When the contents of the file to be built change, a new one is generatedhashValue, and changes to this file do not affect other files in the same module. (Granularity the contents of each file)

If webpack uses a hash name, does it overwrite the generated hash every time

There are three cases:

  • If it ishashIf it is related to the whole project, if one file is changed, all files arehashThey all change and they all share onehashValue;
  • If it ischunkhashIf so, only withentryEach entry file is associated with, that is, the samechunkThe following files have been modifiedchunkUnder the filehashThe value will change
  • If it iscontenthashFor each generated file, a new file will be generated only if the content of the file to be built changeshashValue does not affect other files.

8 How to deal with pictures in Webpack?

There are two image-handling loaders in Webpack:

  • file-loader: solveCSSAnd so on to introduce the picture path problem; (Settled throughurl.import/require()Etc.)
  • url-loader: When the picture is smaller than the setlimitParameter value,url-loaderTake the picturebase64Encoding (when the project has a lot of pictures, passurl-loaderforbase64It’s going to decrease after codinghttpNumber of requests to improve performance), is greater than the limit valuefile-loaderCopy the image and export it to the compile directory.

Abstract syntax tree AST

Abstract Syntax Tree, which parses code letter by letter into Tree object form. This is the basis for translation between languages, code syntax checking, code style checking, code formatting, code highlighting, code error prompts, code auto-completion, and so on

Function square(n) {return n * n} const element = {type: "FunctionDeclaration", start: 0, end: 35, id: Identifier, expression: false, generator: false, params: [1, element], body: BlockStatement }Copy the code

10. What are the problems with using babel-loader? How can you optimize it?

  1. It makes compiling slow. The solution can be found inwebpackthebabel-loaderUse in configurationexcludeThis option is used to remove folders that do not need to be compiled (e.gnode_modulesandbower_components), the other can be setcacheDirectoryOptions fortrueIf caching is enabled, the result of translation will be cached in the file systembabel-loaderAt least twice as fast (the more code you have, the more noticeable it is).
  2. babel-loaderThe size of the packaged file is too large. Babel uses very small helper code for some public methods, such as_extend. By default, it is added to every file that needs it, resulting in a large package file. Solution: Introducebabel runtimeAs a separate module to avoid duplication. In other words, it can be used@babel/plugin-transform-runtimeandbabel-runtime.

11. The Babel principle

The compilation process of Babel is divided into three stages: parsing, transforming, and generating. Take ES6 compilation to ES5 as an example:

  1. ES6Code input;
  2. babylonThe AST is obtained by analysis.
  3. plugin 用 babel-traverse 对 ASTTree traversal compilation, get newASTTree;
  4. withbabel-generatorthroughASTTree generatedES5The code.

12. How does Babel compile Class?

For the class below:

class Person {
  constructor ({ name }) {
    this.name = name
    this.getSex = function () {
      return 'boy'
    }
  }
  getName () {
    return this.name
  }
  static getLook () {
    return 'sunshine'
  }
}
Copy the code

When we are using these Plugins of Babel or preset, there is a configuration property loose which defaults to false in such conditions:

Class compiled:

  • On the wholeClassIt’s going to be encapsulated into oneIIFEExecute function immediately
  • The immediately executed function returns a constructor with the same name as the class
  • Instance properties and methods are defined in constructors (e.gnameandgetSex())
  • Property methods declared inside a class (getName) and static property methods (getLook) will beObject.definePropertySets its enumerable property tofalse

Compiled code:

"use strict"; function _classCallCheck(instance, Constructor) { if (! (instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } var Person = /*#__PURE__*/ (function () { function Person(_ref) { var name = _ref.name; _classCallCheck(this, Person); this.name = name; this.getSex = function () { return "boy"; }; } _createClass( Person, [ { key: "getName", value: function getName() { return this.name; }, }, ], [ { key: "getLook", value: function getLook() { return "sunshine"; }, }, ] ); return Person; }) ();Copy the code

Why does Babel use object.defineProperty for class processing? Is it any different than using the prototype chain directly?

  • Properties and methods declared through stereotype chains are enumerable, that is, can befor... of...To search for
  • Methods declared inside a class are not enumerable

So, in order to conform to the true semantics of ES6, Babel compiles classes using Object.defineProperty to define prototype methods.

However, by setting Babel loose mode to true, it does not strictly follow ES6 semantics and compiles code in a way that is more consistent with how we normally write code. You can set babelrc as follows:

{
  "presets": [["env", { "loose": true }]]
}
Copy the code

For example, the property methods of the Person class above will be compiled to declare methods directly on the prototype chain:

"use strict"; var Person = /*#__PURE__*/function () { function Person(_ref) { var name = _ref.name; this.name = name; this.getSex = function () { return 'boy'; }; } var _proto = Person.prototype; _proto.getName = function getName() { return this.name; }; Person.getLook = function getLook() { return 'sunshine'; }; return Person; } ();Copy the code

conclusion

  • Loose is false by default when compiling with Babel, i.e., non-loose mode

  • In either case, the transformed property methods defined inside the class are defined on the constructor’s prototype object; Static properties are defined on constructors

  • Except in non-looser mode, these property methods are handled by the _createClass function, which sets the property’s enumerable value to False via Object.defineProperty()

  • Since Object is used in the _createClass function, there are side effects in non-loose mode, but not in loose mode.

  • UglifyJS in Webpack will still consider loose mode as having side effects, while Rollup has the function of program flow analysis to better judge whether the code really has side effects, so it will consider loose mode as having no side effects.

    (A side effect is roughly defined as the behavior of a function that affects, or may affect, variables outside the function.)

13. What is Tree Shaking

Understanding tree-shaking

Function:

It means that some useless code is removed during packaging

Principle:

  • ES6Module introduction is statically analyzed, so you can correctly determine which modules are loaded at compile time
  • Analyze the program flow to determine which variables are not used or referenced, and then delete the code

Features:

  • It is turned on by default in production mode, but due to thebabelCompile all modules are encapsulated intoIIFEIt has side effects that cannot betree-shakingoff
  • Can be found inpackage.jsonIn the configurationsideEffectsTo specify which files have side effects. It has two values, one is of Boolean type, if yesfalseAll files have no side effects. If it is an array, the file path in the array indicates that changing the file has side effects
  • rollupandwebpackIn thetree-shakingDifferent levels of, e.gbabelAfter the translationclassIf thebabelThe translation of is in loose mode (i.eloosefortrue),webpackThey still think it has side effects notree-shakingOff,rollupWill be. This is becauserollupThere is flow analysis to better determine whether the code is actually causing side effects.

The principle of

  • ES6 ModuleIntroduce static analysis, so compile time to determine exactly which modules are loaded
  • Statically analyze the program flow to determine which modules and variables are not being used or referenced, and then remove the corresponding code

Rely on the import/export

Conditional retrieval is performed by importing all packages. As follows:

import foo from "foo";
import bar from "bar";

if(condition) {
    // foo.xxxx
} else {
    // bar.xxx
}
Copy the code

ES6’s import syntax is perfect to use tree shaking, because you can analyze unnecessary code when it’s not running

The dynamic feature modules of CommonJS mean that Tree Shaking does not apply. Because it is impossible to determine which modules are needed or not before they actually run. In ES6, a completely static import syntax is introduced: import. This also means that the following imports are not available:

If (condition) {myDynamicModule = require("foo"); } else { myDynamicModule = require("bar"); }Copy the code

14. Vite

Is a browser based on the native ES module import development server, in the development environment, the use of the browser to parse import, on the server side according to the need to compile and return, completely skipped the concept of packaging, the server is ready to use. At the same time, Vue files are not only supported, but also support hot update, and the speed of hot update does not slow down with the increase of modules. Use Rollup packaging in a production environment

Vite characteristics

  • Dev Server starts immediately without waiting;
  • Module hot update in near real time;
  • Compile required files as needed to avoid compiling files that are not needed;
  • Out of the box, avoid various Loader and Plugin configuration;

Out of the box

  • TypeScript– Built-in support
  • less/sass/stylus/postcss– Built-in support (separate compiler installation required)

Does the production environment need to be packaged

You don’t need to pack, you need to start the server, you need browser support

Handwritten implementation

Vite core functions: Static Server + Compile + HMR

Core ideas:

  • Use the current project directory as the root of the static file server

  • Intercept partial file requests

    • Processing codeimport node_modulesIn the module
    • To deal withvueSingle file component (SFC) compilation
  • Implement HMR through WebSocket

#! /usr/bin/env node const path = require('path') const { Readable } = require('stream') const Koa = require('koa') const send = require('koa-send') const compilerSfc = require('@vue/compiler-sfc') const cwd = process.cwd() const streamToString = stream => new Promise((resolve, reject) => { const chunks = [] stream.on('data', chunk => chunks.push(chunk)) stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))) stream.on('error', Reject)}) const app = new Koa() // override request path, /@modules/xxx => /node_modules/ app.use(async (ctx, next) => { if (ctx.path.startsWith('/@modules/')) { const moduleName = ctx.path.substr(10) // => vue const modulePkg = require(path.join(cwd, 'node_modules', moduleName, 'package.json')) ctx.path = path.join('/node_modules', moduleName, Modulepkg.module)} await next()}) // Index.html app.use(async (CTX, next) => { // ctx.path // http://localhost:3080/ // ctx.body = 'my-vite' await send(ctx, ctx.path, { root: cwd, index: 'index.html'}) // There may be additional processing of the corresponding result await next()}) //.vue file request processing, Use (async (CTX, next) => { if (ctx.path.endsWith('.vue')) { const contents = await streamToString(ctx.body) const { descriptor } = compilerSfc.parse(contents) let code if (ctx.query.type === undefined) { code = descriptor.script.content code = code.replace(/export\s+default\s+/, 'const __script = ') code += ` import { render as __render } from "${ctx.path}? type=template" __script.render = __render export default __script` // console.log(code) ctx.type = 'application/javascript' ctx.body = Readable.from(Buffer.from(code)) } else if (ctx.query.type === 'template') { const templateRender = compilerSfc.compileTemplate({ source: descriptor.template.content }) code = templateRender.code } ctx.type = 'application/javascript' ctx.body = Readable.from(buffer. from(code))} await next()}) // Replace special positions in code app.use(async (CTX, next) => { if (ctx.type === 'application/javascript') { const contents = await streamToString(ctx.body) ctx.body = contents .replace(/(from\s+['"])(?! [./])/g, '$1/@modules/') .replace(/process.env.NODE_ENV/g, '"production"') } }) app.listen(3080) console.log('Server running @ http://localhost:3080')Copy the code