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:
Vite
The default entry for packaged resources is HTML, which is required for our micro front-end solutionJS
For the entrance- JS package export code for entry scheme has been removed
import.meta
The statement package is translated into{}
An empty objectchunk
After the separationCSS
File,Vite
The default todocument.head.appendChild
To deal with- After packaging
CSS
The file defaults tomain.js
There is no reference in - The resource path is written manually
new 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 contentsgenerateBundle
– The generation phase of a code block that has already been compiled
Style insert node processing
Problem four, document. Head. The appendChild processing
- use
transform
Hook, replaceVite
The defaultdocument.head.appendChild
Is a custom node cssCodeSplit
Package 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
- use
HTML
Package mode, extractindex.html
In theJS
、CSS
The files are processed separately - Do not add a style file name
hash
To fix the style name by convention - 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
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.