Loading on demand is a basic capability that all component libraries provide, and this article examines the implementation of ElementUI, Vant, and Varlet to help you understand how it works.
Start with a simple component library
We have copied two components from ElementUI: Alert and Tag, and named our component library XUI. The current directory structure is as follows:
Each component is a separate folder. The basic structure is a JS file and a vue file. Components can be registered using Vue.com Ponent or vuue. For example, the Alert js file contains the following contents:
import Alert from './src/main';
Alert.install = function(Vue) {
Vue.component(Alert.name, Alert);
};
export default Alert;
Copy the code
The install method is added to the component so that it can be registered using vue.use (Alert).
The theme files of each component are kept in the /theme-chalk directory, which is also a style file for each component. The index. CSS contains the styles of all components. The source code of ElementUI is SCSS file.
There is also an index.js file on the outermost layer, which is obviously used to export all components as an entry file:
import Alert from './packages/alert/index.js';
import Tag from './packages/tag/index.js';
const components = [
Alert,
Tag
]
const install = function (Vue) {
components.forEach(component= > {
Vue.component(component.name, component);
});
};
if (typeof window! = ='undefined' && window.Vue) {
install(window.Vue);
}
export default {
install,
Alert,
Tag
}
Copy the code
First, introduce all components of the component library in turn, then provide an install method, iterate over all components, use Vue.component method in turn to register, then determine whether there is a global Vue object, if so, it represents the USE of CDN, then automatically register, finally export the install method and all components.
The Vue plug-in is an object with the install method, so we can introduce all the components directly:
import XUI from 'xui'
import 'xui/theme-chalk/index.css'
Vue.use(XUI)
Copy the code
You can also register a component separately:
import XUI from 'xui'
import 'xui/theme-chalk/alert.css'
Vue.use(XUI.Alert)
Copy the code
Why not just import {Alert} form ‘xui’?
Since our component library is not published to NPM, we link our component library globally via the NPM link.
Next, I set up a test project using Vue CLI and run NPM Link XUi to link to the component library. You can then register a component library or a component in the same way as before; here we will only use the Alert component.
It can be found through the test that whether all components are registered or only the Alert component is registered, the contents of the Tag component will be found in the packaged JS:
Next, open the body of this article to see how to remove the Tag.
The simplest introduction on demand
Since each component can be used as a separate plug-in, it is perfectly possible to introduce just one component, such as:
import Alert from 'xui/packages/alert'
import 'xui/theme-chalk/alert.css'
Vue.use(Alert)
Copy the code
This only introduces files related to the alert, and of course only the content of the Alert component will be included. Such a problem is more troublesome, the use of the cost is relatively high, the most ideal way or the following:
import { Alert } from 'xui'
Copy the code
Through the Babel plugin
Using the Babel plug-in is currently the way most component library implementations are introduced on demand. ElementUI uses the babel-plugin-Component:
You can use import {Alert} form ‘xui’ to import the Alert component directly. You don’t need to import styles manually.
The principle is simple. What we want is this:
import { Alert } from 'xui'
Copy the code
But actual on-demand use requires this:
import Alert from 'xui/packages/alert'
Copy the code
Obviously, all we need to do is convert the user from the first method to the second one, and the Babel plug-in does not make sense to the user.
Add a new babel-plugin-component.js file to the babel.config.js file as our plugin file and modify the babel.config.js file:
module.exports = {
// ...
plugins: ['./babel-plugin-component.js']}Copy the code
Using relative paths to reference our plug-in, we can then have fun coding.
Let’s look at the AST tree where import {Alert} from ‘xui’ corresponds
Value can be used to determine the source of the import. Import variables can be found in the speciFIERS array. Each variable is an ImportSpecifier, and there are two objects in it: ImportSpecifier. Imported and ImportSpecifier. Local.
import { Alert } from 'xui'
Copy the code
Imported is the same as local, but if you use an alias:
import { Alert as a } from 'xui'
Copy the code
So this is it:
So let’s just keep it simple and not worry about the alias case, but just use imported.
Import Alert from ‘XUi /packages/ Alert’ AST
Now that the target AST structure is clear, it’s easy to iterate through the speciFIERS array to create new importDeclaration nodes, and then replace the original ones:
// babel-plugin-component.js
module.exports = ({ types }) = > {
return {
visitor: {
ImportDeclaration(path) {
const {
node
} = path
const {
value
} = node.source
if (value === 'xui') {
// Find the list of imported component names
let specifiersList = []
node.specifiers.forEach(spec= > {
if (types.isImportSpecifier(spec)) {
specifiersList.push(spec.imported.name)
}
})
// Create an import statement for each component
const importDeclarationList = specifiersList.map((name) = > {
// The folder name starts with a lowercase letter
let lowerCaseName = name.toLowerCase()
// Construct the importDeclaration node
return types.importDeclaration([
types.importDefaultSpecifier(types.identifier(name))
], types.stringLiteral('xui/packages/' + lowerCaseName))
})
// Replace single node with multiple nodes
path.replaceWithMultiple(importDeclarationList)
}
}
},
}
}
Copy the code
The next package test results are as follows:
You can see that the contents of the Tag component are gone.
Of course, the above implementation is only a simple demo, but there are also various issues to consider: the introduction of styles, aliasing, de-duplication, the introduction of components, the introduction of a component that is not actually used, etc. You can read the source code of babel-plugin-Component directly.
Vant and ANTD also use this method, but with different plugins. Both use babel-plugin-import, which is fork from babel-plugin-import.
The Tree Shaking way
The Vant component library supports Tree Shaking as well as loading on demand using the Babel plugin. It is easy to implement. The final code released by Vant provides the source code for three specifications, commonJS, UMD, esModule, as shown below:
The commonJS specification is the most common way to use it. Umd is used for CDN applications. Esmodule is used for Tree Shaking. The reason is that esModule is statically compiled, which means that at compile time you can determine what a module exported and introduced, and the code execution stage does not change, so the packaging tool can analyze which methods are used and which are not, and can be safely deleted.
Let’s change our component library to support Tree Shaking as well. Since our component is an ESModule module itself, we don’t need to change it, but we need to change the exported file index.js, because exporting is not supported yet:
import { Alert } from 'xui'
Copy the code
Add the following code:
// index.js
// ...
export {
Alert,
Tag
}
// ...
Copy the code
The package.json field is used to indicate the package entry file, so should we just point this field to the esModule entry file? Because it usually points to the CommonJS module entry, and a package may support both nodeJS and Web environments, nodeJS environments may not support esModule modules. Since you cannot modify the old fields, you have to introduce a new field, pkg.module. So modify the package.json file as follows:
// package.json
{
// ...
"mains": "index.js"."module": "index.js".// Add this field
// ...
}
Copy the code
Since we only have esModule modules in our component library, these two fields actually point to the same thing. In practice, you will need to compile to different types of modules like Vant, and modules published to NPM will generally need to be compiled to ES5 syntax. Since these are not the focus of this article, this step is omitted.
Module field is added. If the packaging tool recognizes this field, it will use the esModule specification code in preference. However, this does not end.
import 'core-js'
import 'style.css'
Copy the code
These two files are only introduced, but not obviously used, can you delete them? Obviously not, this is called “side effects”, so we need to tell the packaging tool which files have no side effects, can be deleted, and which files are existing, keep them for me, Vue CLI uses Webpack. We need to add a sideEffects field to the package.json file:
// package.json
{
// ...
"sideEffects": ["**/*.css"].// ...
}
Copy the code
The only side effects in our component library are the style files.
Next, the package test found that the content of the Tag component that was not introduced has been removed:
For more on Tree Shaking, read Tree Shaking.
Use the unplugin-vue-Components plug-in
The unplugin-vue-components plugin is used in the import on Demand section of the varlet library:
The advantage of this way is completely don’t need to introduce the component, directly in the template to use, introduced by the plugin to scan and register, the plug-in built-in support for many popular component libraries on the market, for the protection of the component library has built-in support for direct reference above into a corresponding analytic function configuration can, but it does not support our little broken component library, So you need to write the parser yourself.
The first thing this plugin does is help us import components and register them. In fact, the on-demand functionality still depends on the first two methods.
Tree Shaking
Json module and sideEffects configuration. Then remove the imported and registered code from main.js. Then modify the vue.config.js file. Because the official documentation for this plugin is quite succinct, I refer to the built-in Vant parser to modify it:
ImportName indicates the name of the imported component, such as Alert, path indicates where it was imported, xui for our component library, sideEffects is a file with sideEffects, and the corresponding style file path is configured.
// vue.config.js
const Components = require('unplugin-vue-components/webpack')
module.exports = {
configureWebpack: {
plugins: [
Components({
resolvers: [{
type: "component".resolve: (name) = > {
if (name.startsWith("X")) {
const partialName = name.slice(1);
return {
importName: partialName,
path: "xui".sideEffects: 'xui/theme-chalk/' + partialName.toLowerCase() + '.css'}; }}}]})]}}Copy the code
For example, ElAlert is changed to XAlert, and the template also needs to be changed to x-alert.
You can see that it is working fine, and that the packaging has successfully removed the unused Tag component content.
Separate into
Module and PKG. sideEffects fields are removed, and then the index.js file for each component is modified to support the following import:
import { Alert } from 'xui/packages/alert'
Copy the code
The Alert component is modified as follows:
// index.js
import Alert from './src/main';
Alert.install = function(Vue) {
Vue.component(Alert.name, Alert);
};
// Add the following two lines
export {
Alert
}
export default Alert;
Copy the code
Next, modify our parser:
const Components = require('unplugin-vue-components/webpack')
module.exports = {
configureWebpack: {
mode: 'production'.plugins: [
Components({
resolvers: [{
type: "component".resolve: (name) = > {
if (name.startsWith("X")) {
const partialName = name.slice(1);
return {
importName: partialName,
// Change the path field to point to index.js for each component
path: "xui/packages/" + partialName.toLowerCase(),
sideEffects: 'xui/theme-chalk/' + partialName.toLowerCase() + '.css'}; }}}]})]}}Copy the code
In fact, the path field was changed to point to each component’s index.js file, and the tests were run and packaged with satisfactory results.
section
This article briefly analyzes the component library implementation on demand introduction of several ways, have a component library development needs of friends can choose, sample code please go to: github.com/wanglin2/Co… .