The recently realized simple, transparent, componentized micro front end scheme feels good on the whole, and has received feedback from many people, which is of great reference value for learning. But there are a lot of friends to use the package configuration of this scheme appeared some problems, work should finish what you start, dig the pit must be perfect. Today I will share the development process of Vite plug-in for micro application solution.

From the passage you can learn:

1. Write a Vite plugin 2. Generate a separate resource file by rollup compilation 3. Import resource path processing 4. Some ideas for solving the problemCopy the code

Trouble spots

To sum up, the following problems occur when using this microfront-end solution in Vite:

  1. ViteThe default entry for packaged resources is HTML, which is required for our micro front-end solutionJSFor the entrance
  2. JS package export code for entry scheme has been removed
  3. import.metaThe statement package is translated into{}An empty object
  4. chunkAfter the separationCSSFile,ViteThe default todocument.head.appendChildTo deal with
  5. After packagingCSSThe file defaults tomain.jsThere is no reference in
  6. The resource path is written manuallynew URL(image, import.meta.url)Too complicated

Resolve the problem through configuration

The first three problems can be solved with Vite. Vite is compatible with rollup configurations

Problem a, modify the JS entry is need to modify the Vite configuration, set up the build. RollupOptions. The input for the SRC/main TSX, Vite will be the default in custom configurations. The main benchmark for entry documents do with packaging, Index.html is no longer generated.

Rollup can be configured with the preserveEntrySignatures and ‘allow-extension’ to ensure that the exported modules are not removed after the loading.

Question 3: After reading the Issue of Vite, many people encountered this problem. At first, they thought that Vite processed it by default. After reading the source code of Vite, they did not find the logic of processing, which should be translated by ESbuild. Therefore, setting build.target to esNext will solve the problem, i.e. import.meta belongs to ES2020, or a specific ES2020 will do.

Configuration:

export default defineConfig({
  build: {
    // ES2020 supports the import.meta syntax
    target: 'es2020'.rollupOptions: {
      // Used to control Rollup attempts to ensure that the entry block has the same export as the base entry module
      preserveEntrySignatures: 'allow-extension'.// Import file
      input: 'src/main.tsx',}}});Copy the code

Write Vite plug-in

We can write a plug-in to encapsulate the above configuration.

A normal Vite plug-in is simple

defineConfig({
  plugins: [{// Write plug-in content
      // You can use the hooks provided by Vite and rollup},]});Copy the code

Plug-ins can do a lot of things, customizing the overall flow of code parsing, compilation, packaging, and output through hooks provided by Vite and Rollup. Plugins are usually not written directly in vite. Config. ts. You can define a method to export the plugins.

export function microWebPlugin() :Plugin {
  // Plug-in hooks
  return {
    name: 'vite-plugin-micro-web'.config() {
      return {
        build: {
          target: 'es2020'.rollupOptions: {
            preserveEntrySignatures: 'allow-extension'.input: 'src/main.tsx',}}}; }}; }Copy the code

This is a simple plug-in.

Vite unique hooks

  • Config – called before the Vite configuration is parsed, it can return a partial configuration object that will be deeply merged into the existing configuration, or change the configuration directly
  • ConfigResolved – Called after the Vite configuration has been resolved, using this hook to read and store the final resolved configuration
  • ConfigureServer – is the hook used to configure the development server
  • TransformIndexHtml – Special hook for converting index.html. The hook receives the current HTML string and the conversion context
  • HandleHotUpdate – Performs custom HMR update processing.

The rollup hook

There are many rollup hooks and there are two phases

Compilation stage:

Output stage:

The hooks we will use here are:

  • transform– Used to convert loaded module contents
  • generateBundle– The generation phase of a code block that has already been compiled

Style insert node processing

Problem four, document. Head. The appendChild processing

  1. usetransformHook, replaceViteThe defaultdocument.head.appendChildIs a custom node
  2. cssCodeSplitPackage as a CSS file

By default, cssCodeSplit is packaged as a CSS file, eliminating the need to modify the Vite using the transform plugin.

The fifth problem is that the packaged CSS does not have references. There are various solutions for obtaining the CSS with hash

  1. useHTMLPackage mode, extractindex.htmlIn theJSCSSThe files are processed separately
  2. Do not add a style file namehashTo fix the style name by convention
  3. Extract file name processing through the hook

Finally, the generateBundle phase is used to extract the CSS file name generated by Vite compilation and insert it by modifying the entry code. But generateBundle is already in the output phase and will no longer use the Transform hook. Found a way to get the best of both worlds: create a tiny entry file called main.js that also works with hash and main application timestamp caching.

async generateBundle(options, bundle) {
  // Main entry file
  let entry: string | undefined;
  // All CSS modules
  const cssChunks: string[] = [];
  // Find import/export files and CSS files
  for (const chunkName of Object.keys(bundle)) {
    if (chunkName.includes('main') && chunkName.endsWith('.js')) {
      entry = chunkName;
    }
    if (chunkName.endsWith('.css')) {
      // Use a relative path to avoid subsequent failure of the ESM to resolve modules
      cssChunks.push(`. /${chunkName}`); }}// Use the following code
},
Copy the code

Generate a new entry file

You can now retrieve hash-based JS and CSS entry files via bundle extraction. Now you need to write a new file main.js. Rollup has an API emitFile that triggers the creation of a resource file.

Let’s deal with it:

// Add the above code
if (entry) {
  const cssChunksStr = JSON.stringify(cssChunks);

  // Create minuscule entry files with hash and main application timestamp caching
  this.emitFile({
    fileName: 'main.js'.type: 'asset'.source: '// Add microAppEnv parameters and use relative paths to avoid error import defineApp from './${entry}? microAppEnv'; Function createLink(href) {const link = document.createElement('link'); link.rel = 'stylesheet'; link.href = href; return link; Defineapp. styleInject = (parentNode) => {// Import file export a method to link the packed CSS file into the corresponding node defineapp. styleInject = (parentNode) => {${cssChunksStr}.foreach ((CSS) => {// import.meta.url keeps the path correct, Const link = createLink(new URL(CSS, import.meta[' URL '])); const link = createLink(new URL(CSS, import.meta[' URL '])); parentNode.prepend(link); }); }; export default defineApp; `}); }Copy the code

Plugins need to apply an entry and export a styleInject method to provide style inserts, which we solve by encapsulating the entry method.

Encapsulate a method for application entry calls:

export function defineMicroApp(callback) {
  const defineApp = (container) = > {
    const appConfig = callback(container);
    // Handle style local inserts
    const mountFn = appConfig.mount;
    // Get the method into the plug-in
    const inject = defineApp.styleInject;
    if (mountFn && inject) {
      appConfig.mount = (props) = > {
        mountFn(props);
        // After loading, insert styles
        inject(container);
      };
    }
    return appConfig;
  };

  return defineApp;
}
Copy the code

Now that build generates a main.js file without the hash, the main application can load the packaged resources as normal.

Further optimized, main.js compression obfuscation can be compiled using Vite export transformWithEsbuild:

const result = await transformWithEsbuild(customCode, 'main.js', {
  minify: true});this.emitFile({
  fileName: 'main.js'.type: 'asset'.source: result.code,
});
Copy the code

Subapplication path problem

Previously we needed to manually add new URL(image, import.meta. URL) to fix the child application path problem. This logic is handled automatically through the Transform hook.

Before this plug-in, Vite would convert all resource files to paths

import logo from './logo.svg';

// Convert to:

export default '/src/logo.svg';
Copy the code

So, replace export default “resource path” with export Default new URL(” resource path “, import.meta[‘ URL ‘]).href.

const imagesRE = new RegExp(`\.(png|webp|jpg|gif|jpeg|tiff|svg|bmp)($|\?) `);

transform(code, id) {
  // Fix image resource using absolute address
  if (imagesRE.test(id)) {
    return {
      code: code.replace(
        /(export\s+default)\s+(".+")/.`$1 new URL($2, import.meta['url']).href`
      ),
      map: null}; }return undefined;
},
Copy the code

Complete, a relatively perfect Vite micro application scheme thus born.

Look at the results:

More and more

With plug-ins, unexpected things can be played out. This micro front end solution does not realize the following isolation mode, do not guarantee the future implementation, you can play more imagination.

CSS style isolation

Add ids in the primary application node and modify the CSS through plug-ins

.name {
  color: red;
}

/* converts to */

#id .name {
  color: red;
}
Copy the code

But only if you set a unique ID for each
. And style performance will be affected, cs Modules scheme is better.

JS sandbox

While there is no off-the-shelf solution for doing runtime sandboxes in ESM, the runtime sandbox performance is very poor. Another way to think about it is to start with the compile-time sandbox. Isolate all applied Windows by translating them into sandbox fakeWindows using the Transform hook.

Code sample

You can clone it

Plugin repository: github.com/MinJieLiu/m…

Example microfront-end: github.com/MinJieLiu/m…

Microfront-end solution

Vite micro front-end practice, to achieve a componentized scheme

conclusion

People who are interested in React can join the React group.