Educational background
Know the big guy please light hammer 😂.
This paper is the lead of the main paper produced after the author upgraded the activity structure in the company. Considering the relative independence of this paper, it is pulled out as a separate paper.
The title is dynamic components, but for the sake of understanding it can be called remotely loaded dynamic components, the following unified simplification is called “remote components”.
How do you play it? Don’t worry, listen to me slowly, after watching the Vue component can also play 🐶, you will learn to use a Stylelint plug-in, with DEMO, and hidden eggs at the end.
The author used to work in the advertising Department of our company. Advertising is carried out on scratch cards, big turntables and other activity pages, and then users participate in the shell layer of advertising coupons.
The narrator says: “Remote components have similar applications in visual low-code platforms, and we used similar ideas to decouple the active page and the coupon layer.” Continuing with the theme…
Previous versions of legacy systems were one activity bound to one elastic layer, one to one.
Now the scenario is that an activity can create different styles of elastic layers, which have to be unbound. We need many to many, that is, an activity page can correspond to a number of advertising coupons shell layer, can also correspond to a number of advertising coupons shell layer activity page.
We can write several layers in advance in the local, according to the condition to choose different layers, can satisfy a activity to multiple layers.
The requirement is for the active page to correspond to an infinite number of layers, not multiple, so it is not possible to write all the layers locally. So what to do?
So what we’re going to do is we’re going to look at what we need, and we’re going to look at what we need, and we’re going to return the code from the remote end. This is actually the remote component that we’ll be talking about in this topic.
That’s easy to say. How do you do it?
○ Remote component core
Pure version
If it’s Pure JS and CSS, it’s natural to think that you can create a shell layer by dynamically inserting JS scripts and CSS. Therefore, the compiled JS and CSS files can be stored in the remote CDN.
Looking at the image above, we can see that before the popover comes out, the browser downloads the CSS and JS, and then assembler it into a popover layer based on the code.
/ / CSS inserts
<link rel="stylesheet" href="//yun.xxx.com/xxx.css">
// dynamic insertion of JS
<script type="text/javascript">
var oHead = document.querySelector('.modal-group');
var oScript = document.createElement('script');
oScript.type = "text/javascript";
oScript.src = "//yun.xxx.com/xxx.js";
oHead.appendChild(oScript);
</script>
Copy the code
It can be seen from the above that Pure version of remote components can be realized in JS and CSS, but can it be realized in Vue environment? If the Vue activity is dynamically inserted according to Pure JS and CSS, it can be implemented very rough.
But is there a more elegant way?
Vue version
Selection will not be discussed in detail in this article. The following main article will explain why Vue is chosen.
The above is the legacy system approach, if we are going to migrate the technology stack to Vue, we also need to migrate the remote components, we need to transform it.
Let’s review some concepts of Vue.
Component form
Object Component
A popover, we can actually represent it with a Vue component, we want to put this component in the CDN, download the file directly, and run it in the browser environment? Let’s try it out.
Based on the Vue official document, we can pass the following option objects into Vue and create a component with new Vue.
{
mounted: () = > {
console.log('loading')},template: "<div v-bind:style=\"{ color: 'red', fontSize: '12' + 'px' }\">Home component</div>"
}
Copy the code
With a runtime version that includes the compiler, we can handle templates as strings.
— Runtime – compiler -vs- includes only the runtime
If you need to compile the Template on the client side (such as passing a string to the Template option, or mounting an element with the HTML inside its DOM as a Template), you’ll need to add the compiler, the full version
Seems to have found the door to a new world.
We can indeed implement Template, Script, CSS through this form, but for the development of students, string form of Template, embedded CSS, development experience is not friendly.
Single file Component
The natural thing to think about at this point is SFC-single file components.
The **single-file Components with the.vue extension provide a solution to all of these problems — the Vue document.
But how do you get a.vue component to be downloaded remotely and run in the current active VUE environment? This is a problem because.vue files are not recognized by the browser, but.js files are.
Let’s think about what the.vue file is eventually converted to.
(Photo credit:1.03- VUE file conversion – Brief book)
Through the transformation, it actually becomes a JS object. So how do I convert.vue to.js?
There are two ways, one by runtime conversion, we found http-vue-loader. Get content through Ajax, parse Template, CSS, Script, output a JS object.
For performance and compatibility, we chose to precompile, using a CSS preprocessor and an HTML template precompiler.
Vue officially provides a Vue-Loader, which parses the file, extracts each language block, and if necessary uses other loaders to process them, finally assembling them into an ES Module. Its default export is an object of the Vue.js component option. What does that mean? The official component DEMO is available in the form of option objects.
With the theory behind it, now it’s time to think about the practice. With what compile?
How to build
Since WebPack builds with a lot of modularism-related crap, rollup is usually chosen for small libraries, and we chose rollup here.
// rollup.config.js
import vue from 'rollup-plugin-vue'
import commonjs from 'rollup-plugin-commonjs'
export default {
input: './skin/SkinDemo.vue'.output: {
format: 'iife'.file: './dist/rollup.js'.name: 'MyComponent'
},
plugins: [
commonjs(),
vue()
]
}
Copy the code
Using rollup-plugin-vue, we can convert the.vue file to.js, and rollup compiles the output to the iife form js.
You can see that script, style, and template are processed into corresponding fragments, which will generate a JS object and save it as a.js file. Here is a component options object.
This can be done through the project: github.com/fly0o0/remo… , try the build in the Rollup folder, see the README instructions for details.
We already have an object with the vue. js component option. How do we mount it to the corresponding Vue App?
Mount the way
Back when I was reading through the Vue starter documentation, I came across the concept of a dynamic component, but I didn’t quite understand its usage scenarios.
Dynamic components are components that can be replaced according to rules without being fixed. An option object that supports a component.
Finally realize
First you need to build the.vue file and then load the remote JS via Ajax or dynamic Script. Because Ajax has cross-domain limitations, we choose dynamic Script to load.
The way we just used the Rollup export was to mount the content on a global variable. So, after inserting a dynamic Script, you have a global variable, MyComponent, which you can mount to the dynamic component and finally display the component on the page.
How does it work? What’s missing? First we need a function to load the remote.js component.
// Load the remote component js
function cleanup(script){
if (script.parentNode) script.parentNode.removeChild(script)
script.onload = null
script.onerror = null
script = null
}
function scriptLoad(url) {
const target = document.getElementsByTagName('script') [0] | |document.head
let script = document.createElement('script')
script.src = url
target.parentNode.insertBefore(script, target)
return new Promise((resolve, reject) = > {
script.onload = function () {
resolve()
cleanup(script)
}
script.onerror = function () {
reject(new Error('script load failed'))
cleanup(script)
}
})
}
export default scriptLoad
Copy the code
Then mount the loaded component on the corresponding dynamic component.
<! <template> <component class="remote-test" :is="mode"> </component> </template> <script> import scriptLoad from "./scriptLoad" export default { name: "Remote", data() { return { mode: "", }; }, mounted() { this.mountCom(this.url) }, methods: {async mountCom(URL) {// download remote JS await scriptLoad(URL) // mount in mode this.mode = windox.myComponent // Clear MyComponent window.MyComponent = null }, } } </script>Copy the code
Basically a Vue remote component is implemented, but there is a problem.
The global variable MyComponent needs to be conventions, but conventions should be minimized for a better development experience.
Export way
What’s the solution? Since we use IIFE to export, Rollup also supports UMD, including Common JS and AMD.
We support UMD by configuring Rollup.
// rollup.config.js
import vue from 'rollup-plugin-vue'
import commonjs from 'rollup-plugin-commonjs'
export default {
input: './skin/SkinDemo.vue'.output: {
format: 'umd'.file: './dist/rollup.js'.name: 'MyComponent'
},
plugins: [
commonjs(),
vue()
]
}
Copy the code
As you can see, after the build is complete, three export modes are supported.
We can simulate the Node environment and name the global variables exports and module to get the exported components from the module.exports variable.
The specific implementation core code is as follows.
<! <template> <component class="remote-test" :is="mode"> </component> </template> <script> import scriptLoad from "./scriptLoad" export default { name: "Remote", data() { return { mode: "", }; }, mounted() { this.mountCom(this.url) }, methods: {async mountCom(url) {// simulate node environment window-module. module = {} window-exports = {} // download js await scriptLoad(URL) // mount in mode Exports},}} </script>Copy the code
Finally fixed the Vue version of the remote component loading mode.
The next step is to figure out how to handle the design of remote components (shell layers).
summary
Remote component functionality is implemented through the use of Vue dynamic components, replacing the old architecture.
You can try the remote component shell layer at the following address, following the project’s README. You get the following remote component shell layer.
Project address: github.com/fly0o0/remo…
Design of remote component (shell layer
The remote component has been achieved. This part is mainly about the design of the remote shell layer component.
For the remote single component itself, you just need to render the view based on the data and trigger the business logic based on the user behavior. The overall code logic looks like this.
Component reuse, component communication, component encapsulation, and style hierarchy need to be considered.
First let’s look at component reuse.
In order to facilitate unified management and reduce redundant code, we usually write similar components and extract some common components, such as buttons.
But since the remote single component code is separate from the page side code (which can be understood as the product of the two WebPack entries), we need to think about where the common components should go.
Component reuse
Now you can see that there are three cases, and let’s try to think through them using the enumeration method.
Packaging 📦
Common components are packaged with remote components
Not only will the remote components become larger, but they will not be reused by other remote components. Think about it further.
Common components are packaged separately
Remote components and common components are separately packaged, which is also unfavorable. Since the remote components are separated from less than 5 public components and the amount of code is small, packaging the remote components as a single layer will result in one more post request, which will affect the display of remote components in the first time.
Keep thinking and see.Common components are packaged with the page core library
By packing common components with the core library of the page, you can speed up the presentation of remote components by avoiding loading them later.
So the final option is to package the common components and the page core library together.
If the remote.js component is separated from the public component, how can we use the public component? 😂
Registered 🔑
To review Vue’s official documentation, Vue.com Ponent provides the ability to register components and then reference them globally. Let’s try it out.
Common components such as buttons and closures need to be registered in the following ways.
A button assembly
// Local page side (local compared to remote CDN) <! --> <template> <button type="button" class=" BTN "@click="use"> </button> </template> <script> export default { name: 'Button', inject: ['couponUseCallback'], methods: { use() { this.couponUseCallback && this.couponUseCallback() } } } </script>Copy the code
A shutdown component
// Local page side (local compared to remote CDN) <! <span ></span> </template> <script> export default {name: "CouponClose", inject: ["couponCloseCallback"], methods: { close() { this.couponCloseCallback && this.couponCloseCallback(); ,}}}; </script> <style lang="less" scoped> .close { &.gg { background-image: url("//yun.tuisnake.com/h5-mami/dist/close-gg.png") ! important; background-size: 100% ! important; width: 92px ! important; height: 60px ! important; } } </style>Copy the code
Public components are registered globally with Vue.com Ponent so that we can call them directly from the remote component.
// local page side (local versus remote CDN) <script> Vue.component("CpButton", Button); Vue.component("CpClose", Close); </script>Copy the code
After solving the problem of common component reuse, you need to consider remote components and page containers, as well as communication between different types of remote components.
Component communication
You can think of the page container as the father and the remote component as the son. There is cross-level communication between the parent and the remote component. The father and son also include the grandparent and grandparent cases, so non-props can be supported. How do you handle that?
Events can be triggered and called back by providing itself to the remote component in the core library of the page and injecting active instances in the remote component.
What about different types of remote components? With Event Bus, you can use the top-level page instance as the Event center, and use on and Emit to communicate, reducing the coupling degree between different types of remote components.
Component packaging
Now there is a component encapsulation problem, first look at an example, the basic understanding.
There are three nested components, as shown below. ** ** now needs to assign a count value to the RealComponent from the top component main.vue, then listen for the RealComponent input event, and notify the methods in main.vue if there is a change. How do you do that?
How many options are available for cross-tier communication?
- We use VUex for data management, which is too much for us.
- Custom Vue Bus event bus (as mentioned above), no obvious dependencies for messaging, if the props required for delivering a component are not appropriate.
- The props are passed one by one. However, a large number of events and attributes need to be passed, increasing the maintenance cost.
There is another way to achieve “transparent transmission” of attributes and events across tiers through ATTRs and ATTRs and ATTRs and Listeners.
The main components
// main. vue <template> <div> <h2> {{count}}</h2> <ComponentWrapper @changecount ="changeCount" :count="count"> </ComponentWrapper> </div> </template> <script> import ComponentWrapper from "./ComponentWrapper"; export default { data() { return { count: 100 }; }, components: { ComponentWrapper }, methods: { changeCount(val) { console.log('Top count', val) this.count = val; }}}; </script>Copy the code
Components for packaging
Sometimes we need to wrap components (especially third-party component libraries) in order to add functionality to a real component.
// componentWrapper. vue <template> <div> <h3> <RealComponent v-bind="$attrs" v-on="$listeners"></RealComponent> </div> </template> <script> import RealComponent from "./RealComponent"; Export default {inheritAttrs: false, // Default is true components: {RealComponent}}; </script>Copy the code
Real components
// realcomponent. vue <template> <div> <h3> </h3> <input v-model="myCount" @input="inputHanlder" /> </div> </template> <script> export default { data() { return { myCount: 0 } }, created() { this.myCount = this.$attrs.count; // Pass the attribute console.info(this.$attrs, this.$listeners) to the Main component; }, methods: { inputHanlder() { console.log('Bottom count', this.myCount) this.$emit("changeCount", this.myCount); / / in the component passed in the Main event, top events / / through emit calls this. $listeners. ChangeCount (enclosing myCount) / / or through callbacks}}}; </script>Copy the code
Coming back from the examples in this article, the scenario we will face is as follows.
Remote components actually have two layers, one is local (in-page) and the other is remote (CDN). The local layer is only used for encapsulation, which can be interpreted as wrapping a layer without any actual functionality. In this case, it can be understood that the local component layer is the wrapper layer. The wrapper layer mainly does the function of importing remote components, which cannot be removed. The above features need to be used to pass information to remote components.
The style hierarchy
In this paper, the remote component can be simply understood as the remote shell layer component. The business of the company involves different shell layer categories, and each shell layer category may overlap.
Agreed upon in the z – index
Therefore, 0 to 90 are divided into ten layers. You can increase the value later according to the actual situation, and the z-index of each remote component container can only be specified in the specified layer.
// const.js
const FLOOR = {
MAIN: 0.// The main page container
COUPON_MODAL: 20.// Advertising shell layer
OTHER_MODAL: 30.// Other shell layers
ERROR_MODAL: 90. }Copy the code
Sets the wrapper layer for each remote component, the shell layer.
// CouponModalWrapper.vue <script> <template> <div class="modal-wrapper" :style="{'z-index': FLOOR.COUPON_MODAL}" @touchmove.prevent> <slot></slot> </div> </template> // OtherModalWrapper.vue <template> <div class="modal-wrapper" :style="{'z-index': FLOOR.OTHER_MODAL}" @touchmove. Prevent >< slot></slot> </div> </templateCopy the code
Then each category introduces the corresponding shell layer wrapping layer.
// CouponModal2.vue <template> <CouponModalWrapper>... </CouponModalWrapper> </template> // OtherModal2.vue <template> <OtherModalWrapper> ... </OtherModalWrapper> </template>Copy the code
In this way, you can avoid some problems, but what if someone really wants to make trouble?
Don’t worry. There’s a way.
With the help of stylelint
The idea is that each type of remote component has its own home folder, and you can define the highest and minimum allowable z-index for this folder.
Autoprefixer is based on a postCSS tool that automatically prefixes webkit. Stylelint, a tool we often use to validate CSS formats, is based on it.
We wondered if we could use the ability of stylelint to constrain stylelint, but we found that the official document didn’t have the API we wanted.
We need to develop our own stylelint plug-in to see a basic stylelint plug-in plug-in.
Stylelint takes a function and returns a function through the stylelint.createPlugin method.
const stylelint = require('stylelint');
const ruleName = 'plugin/z-index-range-plugin';
function rule(options) {
// options Specifies the incoming configuration
return (cssRoot, result) = > {
// cssRoot is a postCSS object
};
}
module.exports = stylelint.createPlugin(
ruleName,
rule
);
Copy the code
Function can get PostCSS objects, you can use PostCSS to parse code into AST, traversal, modify, AST change code and other operations.
There are some concepts that we can use.
- Rule, selector, for example. Class {z-index: 99}.
- Decl, property, such as z-index: 99.
We need to check the value of z-index, so we need to go through the CSS to check z-index. We can call cssroot. walkDecls to do the traversal:
/ / traverse
cssRoot.walkDecls((decl) = > {
// Get the attribute definition
if (decl) {
// ... }});Copy the code
The precursors are almost enough.
Suppose we want to check the z-index of a.css file in two folders.
We set the z-index range under the stylelint configuration file of the two modules.
Here we see the stylelint configuration file, two CSS files.
├── class.htm ├── class.htm ├── class.htm ├── class.htm ├── class.htm ├── class.htmCopy the code
Stylelint Configuration file
// .stylelintrc.js
module.exports = {
"extends": "stylelint-config-standard".// Custom plug-in
"plugins": ["./plugin.js"]."rules": {
// Custom plug-in rules
"plugin/z-index-range-plugin": {
// Set the range to ensure that each module is not repeated
"module1": [100.199]."module2": [200.299]}}}Copy the code
CSS test File
/* module1/index.css */
.classA {
color: red;
width: 99px;
height: 100px;
z-index: 99;
}
/* module2/index.css */
.classB {
color: red;
width: 99px;
height: 100px;
z-index: 200;
}
Copy the code
The goal is to run the following command and module1/index.css will report an error saying that z-index is smaller than expected.
npx stylelint "*/index.css"
Copy the code
So we completed the following code, achieved the intended purpose.
const stylelint = require('stylelint');
const ruleName = 'plugin/z-index-range-plugin';
function ruleFn(options) {
return function (cssRoot, result) {
cssRoot.walkDecls('z-index'.function (decl) {
// Traverse the path
const path = decl.source.input.file
// Retrieve the module information from the file path
const match = path.match(/module\d/)
// Get the folder
constfolder = match? .0]
// Obtain the value of z-index
const value = Number(decl.value);
// Get the set maximum and minimum values
const params = {
min: options? .[folder]? .0].max: options? .[folder]? .1],}if (params.max && Math.abs(value) > params.max) {
// Call the report method provided by stylelint to give an error message
stylelint.utils.report({
ruleName,
result,
node: decl,
message: `Expected z-index to have maximum value of ${params.max}. `
});
}
if (params.min && Math.abs(value) < params.min) {
// Call the report method provided by stylelint to give an error message
stylelint.utils.report({
ruleName,
result,
node: decl,
message: `Expected z-index to have minimum value of ${params.min}. `}); }}); }; }module.exports = stylelint.createPlugin(
ruleName,
ruleFn
);
module.exports.ruleName = ruleName;
Copy the code
Try the project: github.com/fly0o0/styl… , have a try to feel 🐶.
This completes the basic design of a long-range shell layer.
But still encountered some problems, difficult 😂.
○ Problems encountered
We excitedly plan to send online, the result reported an error 🐶. Error: webpackJsonp is not a function.
Don’t panic. Eat a melon to calm down. What does webpackJsonp do?
Example of asynchronous loading
Take a look at the following example, which loads test.js using import’s on-demand asynchronous loading feature, and is built on Webpack3.
// Load test.js asynchronously
import('./test').then((say) = > {
say();
});
Copy the code
The asynchronous load file 0.bundle.js is then generated.
// Asynchronously load the file, 0.bundle.js
webpackJsonp(
// ID of the module stored in another file
[0].// The modules contained in this file
[
// The module corresponding to test.js
(function (module.exports) {
function ; (content) {console.log('i am test')}module.exports = say; })]);Copy the code
And execute the entry file bundle.js.
// Execute the entry file, bundle.js
(function (modules) {
/*** * webpackJsonp Used to install modules from asynchronously loaded files. * * /
window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
var moduleId, chunkId, i = 0, resolves = [], result;
for (; i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if (installedChunks[chunkId]) {
resolves.push(installedChunks[chunkId][0]);
}
installedChunks[chunkId] = 0;
}
for (moduleId in moreModules) {
if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { modules[moduleId] = moreModules[moduleId]; }}while(resolves.length) { resolves.shift()(); }};// Simulate the require statement
function __webpack_require__(moduleId) {}/** * is used to load files corresponding to chunks that are split off and need to be loaded asynchronously */
__webpack_require__.e = function requireEnsure(chunkId) {
/ /... Omit code
return promise;
};
return __webpack_require__(__webpack_require__.s = 0); }) ([// The module corresponding to main.js
(function (module.exports, __webpack_require__) {
// Load the Chunk corresponding to show.js asynchronously through __webpack_require__.e
__webpack_require__.e(0).then(__webpack_require__.bind(null.1)).then((show) = > {
// Execute the show function
show('Webpack'); }); })]);Copy the code
As you can see, webpackJsonp is used to load asynchronous module files. But why is webpackJsonp not a function?
Start troubleshooting
We started to examine the built source code and discovered that our webpackJsonp was not a function, but an array (now known as Webpack4, but not at the time of the inspection).
We found that asynchronous files do become arrays when loaded, and we added an asynchronous module to the system by pushing it.
// Load files asynchronously
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[/* chunk id */ 0] and {"./src/async.js": (function(module, __webpack_exports__, __webpack_require__) {
/ /...
}))
Copy the code
We also found that webpackJsonp is defined as an array in the execution entry file.
// Execute the core code in the entry file, bundle.js
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] | | [];var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
jsonpArray.push = webpackJsonpCallback;
jsonpArray = jsonpArray.slice();
for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
var parentJsonpFunction = oldJsonpFunction;
Copy the code
It’s true that the source webpackJsonp we built is an array, not really a function, but feels like a clue. But why should webpackJsonp be used in a functional form?
We suspected that there was something wrong with the error, started to check the error, and found that the corresponding file was actually called using webpackJsonp as a function. What happened? 🤔 ️
At this time, we noticed that the error was reported on the remote components of the old architecture, is there any evidence in the old architecture project?
We began to explore the old architecture and discovered that the old architecture was built using WebPack3, while our new architecture was built using WebPack4. Is there something wrong here? 💡
So we rebuilt the remote components of the old architecture with WebPack3 and found that webpackJsonp actually corresponds to functions, as shown in the “Example of asynchronous loading” in the previous section.
Webpack4 and WebPack3 build new and old asynchronous remote components. WebpackJsonp is an array under version 4 and a function under version 3.
For those of you who are careful, you may have noticed that the above diagram appears before. When the webPack4 build entry file loads the asynchronous component built by WebPack3, you get the error that webpackJsonp is not a function in the section header.
Think about it. There are probably a few options.
- Change the name of webpackJsonp in the asynchronous components built by WebPack3, and then customize the function of the asynchronous loading capability (webpackJsonp function) in the container entry.
- Rebuild webPack4 with all the asynchronous components built from the legacy architecture WebPack3.
- Search for official support, after all, this is a WebPack4 breack changes from WebPack3.
The first solution is a bit heavy, and how to ensure that asynchronous components and entry file synchronization modification is completed? The second solution is also a lot of work, for all the asynchronous components of the old architecture have to be updated, and the reliability of the update is worried in case anything is missed. The third option looks the most plausible.
So under the direction of the third scheme, the search began.
We found jsonpFunction through webPack4 source global search webpackJsonp. The official documentation shows that jsonpFunction is the name of webpackJsonp where you can customize WebPack4. For example, you could change it to something like this.
output: {
// Customize the name
jsonpFunction: 'webpack4JsonpIsArray'
},
Copy the code
In this case, webpackJsonp is not an array, but undefined. So we need to provide the definition of the webpackJsonp function version in our common code base. As mentioned in the asynchronous Loading examples section.
// webpackJsonp function version! (function (n) {
window.webpackJsonp = function (t, u, i) {
/ /...
}
}([]))
Copy the code
This provides the ability for the entry page to load asynchronous files built by WebPack3.
Evolution of a.
We have also made some evolutions of the remote component shell layer, which is not relevant to this article.
Image compression problem
Coupon shell layer has PNG, JPG, GIF format, the need for faster display speed, so we did a unified service image compression.
GIF processing policy: github.com/kornelski/g… PNG processing policy: pngQuant.org
The efficiency problem
Regular remote components can be handled by building tools, so we built visual low-code building tools, interested students to leave a message, I consider writing a 😂.
○ Read More
Remote components
Yangjunlong /compile-vue-demo: compile vUE single file components
Webpack loads asynchronously
How does Webpack work? (2) – Zhihu
Webpack principle – output file analysis – tencent Web front-end IMWeb team community | | blog blog