Hangzhou Youzan e-commerce team is short of 10+ HC, covering front-end, Java, testing, interested in welcome to contact [email protected] or directly contact WX: WSLDD225
preface
Since using the module system of ES6, import Export Default has been happily used in various places, but you can also see require Module. exports in older projects using the CommonJS specification. Even sometimes it’s common to see the two interworking. There is no problem with the use, but the correlation and distinction cannot be solved, and the use is confused. Such as:
- Why is it used in some places
require
To reference a moduledefault
?require('xx').default
- This is often seen in the documentation referenced by various UI components
import { button } from 'xx-ui'
This introduces all the component content, requiring the addition of additional Babel configurations, such asbabel-plugin-component
? - Why is it possible to use es6 imports to refer to modules defined by the CommonJS specification, or vice versa?
- When we browse some UI component modules downloaded by NPM (such as the element-UI lib file), we can see the webpack compiled JS file, which can be referenced using import or require. But we usually compiled JS can not be imported by other modules, why?
- What role does Babel play in the modular scenario? And webpack? Which is the key?
- I hear es6 is still available
tree-shaking
Function, how can I use this function?
If these are all clear to you, it’s time to turn off this post. If in doubt, this post is for you!
Webpack and Babel in modularity
The principle of modularity in WebPack
Webpack itself maintains a module system that is compatible with all front-end history process module specifications, including AMD CommonJS ES6, etc. This article mainly describes the CommonJS ES6 specification. The implementation of the modularity is actually in the final compiled file.
I wrote a demo to do a better job.
// webpack
const path = require('path');
module.exports = {
entry: './a.js'.output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',}};Copy the code
// a.js
import a from './c';
export default 'a.js';
console.log(a);
Copy the code
// c.js
export default 333;
Copy the code
(function(modules) {
function __webpack_require__(moduleId) {
var module = {
i: moduleId,
l: false,
exports: {}
};
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
return module.exports;
}
return __webpack_require__(0);
})([
(function(module, __webpack_exports__, __webpack_require__) {// Reference module 1"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__c__ = __webpack_require__(1);
/* harmony default export */ __webpack_exports__["default"] = ('a.js');
console.log(__WEBPACK_IMPORTED_MODULE_0__c__["a"/* default */]); }),function(module, __webpack_exports__, __webpack_require__) {// Output data for this module"use strict";
/* harmony default export */ __webpack_exports__["a"] = (333); })]);Copy the code
The js section above is the webpack compiled code (simplified), which contains the WebPack runtime code, which is the implementation of the module.
If we simplify the code a little bit, we see that this is a self-executing function.
(function(modules) {
})([]);
Copy the code
The input to a self-executing function is an array that contains all the modules wrapped in the function.
The logic in the body of the self-executing function is the logic of the processing module. The key is the __webpack_require__ function, which is a substitute for require or import, and we can see that it is defined and then called in the body of the function. This will pass in a moduleId, 0 in this case, which is the contents of our entry module a.js.
Let’s look inside __webpack_require__ again
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
returnThe module. Exports;Copy the code
That is, we call the first function from the modules array of the incoming arguments and incorporate it into the argument
- module
- module.exports
- webpack_require
Let’s take a look at the logic for the first function (the entry module) :
function (module, __webpack_exports__, __webpack_require__) {
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__c__ = __webpack_require__(1);
/* harmony default export */ __webpack_exports__["default"] = ('a.js');
console.log(__WEBPACK_IMPORTED_MODULE_0__c__["a" /* default */]);
}
Copy the code
We can see that the entry module calls __webpack_require__(1) to refer to the second function in the argument set.
The __webpack_exports__ object that is entered is then added with the default attribute and assigned.
__webpack_exports__ is a module whose module.exports object is passed by reference, indirectly adding attributes to module.exports.
Module. exports return will be exported finally. This completes the __webpack_require__ function.
For example, if you call __webpack_require__(1) in the import module, you get module.exports returned from that module.
But at the bottom of the self-executing function, WebPack returns the output of the entry module as well
return __webpack_require__(0);
Copy the code
Currently, compiled JS exports of import modules (module.exports) have no effect except on the current scope. This JS cannot be referenced by other modules as require or import.
The role of Babel
Arguably, WebPack’s modularity solution has done a good job of converting ES6 modularity into WebPack modularity, but the rest of ES6 syntax still needs to be compatible. Babel is designed to handle es6 conversion to ES5. This also includes es6’s module syntax conversion.
The idea is similar, except that webPack’s native transformation can take an extra step of static analysis, using tree-shaking (described below)
Babel pre-converts es6 module keywords such as import to commonJS specifications. This eliminates the need for WebPack to do any processing and uses the __webpack_require__ processing defined at the WebPack runtime.
So that explains problem 5.
What role does Babel play in the modular scenario? And webpack? Which is the key?
So how does Babel translate es6’s module syntax?
Export module
Es6 export module writing methods are
export default 123;
export const a = 123;
const b = 3;
const c = 4;
export { b, c };
Copy the code
Babel will convert all of these to CommonJS exports.
exports.default = 123;
exports.a = 123;
exports.b = 3;
exports.c = 4;
exports.__esModule = true;
Copy the code
Babel converts es6’s module output logic to be very simple. It assigns all output to exports, with the __esModule flag indicating that this is a CommonJS output converted from ES6.
After Babel converts a module’s exports to the CommonJS specification, it also converts imported imports to the CommonJS specification. That is, require is used to reference modules, and then certain processing is performed to meet the use intention of ES6.
The introduction of the default
For the most common
import a from './a.js';
Copy the code
Import a from ‘./a.js’ in ES6 is intended to import the default output of an ES6 module.
Var a = require(./a.js); var a = require(./a.js);
As we mentioned in the export, the default output is assigned to the default property of the exported object.
exports.default = 123;
Copy the code
So Babel adds a help _interopRequireDefault function.
function _interopRequireDefault(obj) {
return obj && obj.__esModule
? obj
: { 'default': obj };
}
var _a = require('assert');
var _a2 = _interopRequireDefault(_a);
var a = _a2['default'];
Copy the code
So the last a variable here is the default property of the value of require. If it was originally a CommonJS specification module, it is the export object of that module.
Introduce the * wildcard
The import * as a from ‘./a.js ‘es6 syntax is intended to package all the named output and defalut output of the ES6 module into a single object and assign it to the a variable.
Known as the CommonJS specification export:
exports.default = 123;
exports.a = 123;
exports.b = 3;
exports.__esModule = true;
Copy the code
Var a = require(‘./a.js’) import this object for es6 converted output.
So just return this object.
if (obj && obj.__esModule) {
return obj;
}
Copy the code
If a commonJS module is exported without the default property, add a default property and assign the entire module object to the default property again.
function _interopRequireWildcard(obj) {
if (obj && obj.__esModule) {
return obj;
}
else {
var newObj = {}; // (A)
if(obj ! = null) {for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key))
newObj[key] = obj[key];
}
}
newObj.default = obj;
returnnewObj; }}Copy the code
import { a } from ‘./a.js’
Require (‘./a.js’).a
conclusion
After the above conversion analysis, we know that even if we use the ES6 module system, with Babel’s conversion, the ES6 module system will eventually be converted to the CommonJS specification. So if we use Babel to convert the ES6 module, we can mix the ES6 module with the CommonJS specification, because we will eventually convert to CommonJS.
This explains problem 3
Why is it possible to use es6 imports to refer to modules defined by the CommonJS specification, or vice versa?
babel5 & babel6
As mentioned in Babel’s conversion of export modules above, es6’s export Default will be converted to exports.default, even if the module has only one output.
This also explains problem number one
Why do some places use require to reference a module with default? require(‘xx’).default
We often use es6’s export Default to output a module, and this output is the only output of the module. We mistakenly think this is the default output of the module.
// a.js
export default 123;
Copy the code
Var foo = require('./a.js')
Copy the code
When referencing with require, we also mistakenly think we’re importing the default output of file A.
Var foo = require(‘./a.js’).default
This scenario is often encountered when writing webPack code segmentation logic.
require.ensure([], (require) => {
callback(null, [
require('./src/pages/profitList').default,
]);
});
Copy the code
This is a change from Babel6, which was not the case with Babel5.
Babeljs. IO/docs/plugin…
In the babel5 era, most people used require to refer to the default output of ES6 as the default output of a module, so Babel5 did a hack on this logic. If an ES6 module has only one default output, it is also assigned to module.exports when converted to CommonJS, i.e. the entire exported object is assigned the value of default.
The default value of require(‘./a.js’) is the desired default value.
This does not fit the es6 definition, where default is just a name and has no meaning.
export default = 123;
export const a = 123;
Copy the code
This means the same thing: output variables named default and a, respectively.
Another important problem is that once another named output is added to the a.js file, the importing party will be in trouble.
// a.js
export default 123;
exportconst a = 123; / / newCopy the code
// b.js
var foo = require('./a.js'); {default: 123, a: 123}}Copy the code
So babel6 has removed this hack and it’s the right decision. Any incompatibilities caused by updating Babel6 can be addressed by introducing babel-plugin-add-mole-exports.
How can webPack compiled JS be referenced by other modules
The webpack configuration is given in the WebPack Modularity principles section, and the compiled JS cannot be referenced by other modules. Webpack provides the output. LibraryTarget configuration to specify the purpose of the built JS.
The default var
If output. Library = ‘test’ is specified, module.exports returned by the entry module is exposed to global var test = returned_module_exports
commonjs
Exports [‘spon-ui’]
commonjs2
Module. exports returned by import modules are assigned to module.exports
So the element-UI is built using CommonJs2, and the js of the exported component is eventually assigned to module.exports for reference by other modules.
This explains problem 4
When we browse some UI component modules downloaded by NPM (such as the element-UI lib file), we can see the webpack compiled JS file, which can be referenced using import or require. But we usually compiled JS can not be imported by other modules, why?
Optimization of module dependencies
The principle of on-demand loading
To avoid importing all files, use Babel plug-ins such as babel-plugin-Component when using each of the major UI component libraries.
import { Button, Select } from 'element-ui'
Copy the code
Import is converted to CommonJS first, i.e
var a = require('element-ui');
var Button = a.Button;
var Select = a.Select;
Copy the code
var a = require(‘element-ui’); This process will bring in all the components.
So babel-plugin-Component does one thing and converts import {Button, Select} from ‘element-ui’ to
import Button from 'element-ui/lib/button'
import Select from 'element-ui/lib/select'
Copy the code
Even if it is converted to the CommonJS specification, it will only introduce its own component’s JS, minimizing the amount of import.
So we’ll see that almost all UI component libraries are in the form of directories
|-lib
||--component1
||--component2
||--component3
|-index.common.js
Copy the code
Index.mon.js calls all components in the form of import Element from ‘element-ui’.
Components under lib are referenced on demand.
So that explains problem 2
Import {button} from ‘xx-ui’. This will import all the components and will require additional Babel configuration, such as babel-plugin-component?
tree-shaking
Webpack2 begins with the introduction of tree-shaking technology, which can remove unused modules by statically analyzing the syntax of ES6. It only works for ES6 modules, so once Babel converts es6 modules to CommonJS, WebPack2 will not be able to use this optimization. So to use this technique, we can only use WebPack’s module handling, plus Babel’s ES6 conversion capability (with module conversion turned off).
The most convenient way to use Babel is to modify the configuration of Babel.
use: {
loader: 'babel-loader',
options: {
presets: [['babel-preset-es2015', {modules: false}}}]],Copy the code
Modify the initial demo
// webpack
const path = require('path');
module.exports = {
entry: './a.js'.output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',},module: {
rules: [{test: /\.js$/.exclude: /(node_modules|bower_components)/.use: {
loader: 'babel-loader'.options: {
presets: [['babel-preset-es2015', {modules: false}]],}}}]}};Copy the code
// a.js
import a from './c';
export default 'a.js';
console.log(a);
Copy the code
// c.js
export default 333;
const foo = 123;
export { foo };
Copy the code
The change is to add Babel and disable its modules function. Then add an output export {foo} in C.js, but it is not referenced in A.js.
Finally, in the compiled JS, the c.js module is as follows:
"use strict";
/* unused harmony export foo */
/* harmony default export */ __webpack_exports__["a"] = (333);
var foo = 123;
Copy the code
The foo variable is marked as unused and will be removed during final compression.
It should be noted that even if ES6 was used when the module was introduced and the module was output using CommonJS, it would not be possible to use tree-shaking.
Most third-party libraries follow the CommonJS specification, which makes the introduction of third-party libraries unable to reduce unnecessary introduction.
So for the future, third-party libraries will publish modules in both CommonJS and ES6 formats. The entry to an ES6 module is specified by the package.json field module. Commonjs is still specified in the main field.
So that explains problem number six
I heard that ES6 also has tree-shaking. How can I use it?
19 year goal: Eliminate English! I opened a new official account to record a programmer’s learning English journey
Csenglish programmers learn English, spend 10 minutes a day to hand in homework, learn English with me