Problems with modular development
- Naming conflicts
- File dependency
- Low scalability
- Low reusability
- And so on…
First, the evolution process of modularity
From the simplest operations of addition, subtraction, multiplication and division to illustrate, ES6 syntax is not used here for ease of understanding.
-
The way global functions are written — the most primitive way
// The early development process was to wrap the reused code into functions // and then put a series of functions into a file, called modules // Disadvantages: there are naming conflicts and low maintainability // Just from the code point of view: Function convertor(a) {return parseFloat(a); } function add(a, b) { return convertor(a) + convertor(b); }Copy the code
-
How objects are encapsulated
Var calculator = {add: function (a, b) { return this.convertor(a) + this.convertor(b); }, convertor:function(a){ return parseFloat(a) } };Copy the code
-
Division of private space
// High cohesion, low coupling: modules are highly correlated within the module, there is not much correlation between modules, such as convertor and add // disadvantages: Extensibility low var Calculator = (function () {// Privatize a member, external cannot access and modify function convertor(a) {return parseFloat(a); Function add(a, b) {return convertor(a) + convertor(b); } return { add:add } })();Copy the code
-
Module extension
// calc_v2016.js (function (window,calculator) { function convert(input) { return parseFloat(input); } calculator = { add: function (a, b) { return convert(a) + convert(b); } } window.calculator = calculator; })(window, {}); // New demand remain // calc_v2017.js // Open and close principle: Calculator open for additions, close for modifications (function (Calculator) {function convert(input) {return parseInt(input); Remain = function (a, b) {return convert(a) % convert(b); // Calculator exists, I am an extension, I am a new addition; } window.calculator = calculator; })(window.calculator || {});Copy the code
-
Third party dependencies
// calc_v2016.js (function (window,calculator) {// console.log(document) cannot be used in this way; function convert(input) { return parseFloat(input); } calculator = { add: function (a, b) { return convert(a) + convert(b); } } window.calculator = calculator; })(window, {}); // Calc_v2017.js (function (calculator,document) {// Depend on the parameters of the function, which belongs to the module internal console.log(document); function convert(input) { return parseInt(input); } calculator.remain = function (a, b) { return convert(a) % convert(b); } window.calculator = calculator; })(window.calculator || {},document);Copy the code
The above introduces the general situation of modularity development through some brief code.
Second, modular specification
- Server-side specification CommonJS- nodeJS
- Browser side specification AMD-requireJS CMD-SeaJS
- ES6 implements module functions on the level of language standards, and the implementation is quite simple, aiming to become a common module solution for browsers and servers. Its module functions are mainly composed of two commands:
export
和import
.export
Command is used to specify the external interface of a module.import
The command is used to enter functions provided by other modules. I won’t show you how to write each specification.
CommonJS module specification
-
Node uses a modular structure that follows the CommonJS specification
- Module and file are one-to-one correspondence, that is, to load a module is to load a corresponding module file.
- CommonJS is a set of convention standards, not technology; A structure used to dictate how our code should look.
-
CommonJS module features
- All code runs in the module scope and does not pollute the global scope.
- Modules can be loaded multiple times, but only run once on the first load, and then run results are cached, and read directly from the cache after loading;
module.exports
The module will not be executed again. - Modules are loaded in the order they appear in the code.
-
Classification of modules
- Custom module: we write our own function module files.
- Core module: Node platform comes with a set of basic function modules.
- Third party modules: Functional modules developed by the community or third parties can be directly brought back for use.
-
Module definition
-
Node provides a Module builder function inside. All modules are instances of Module with the following attributes:
module.id
The module’s identifier, usually the module’s file name with an absolute path.module.filename
The absolute path to the file defined by the module.module.loaded
Returns a Boolean value indicating whether the module has finished loading.module.parent
Returns an object representing the module that called the module.module.children
Returns an array representing other modules used by this module.module.exports
Represents the output value of the module.
-
Loading a Module is building an instance of a Module, and a new JS file is a Module
// Export mode, 'module.exports' and' exports' exports.name = value; module.exports = { name: value };Copy the code
Module. exports is an interface used to export members for modules; Exports is an alias pointing to module.exports, which is equivalent to executing var exports = module.exports when the module starts.
-
-
Write a simple require using Node
function $require(files) { const fs = require('fs'); const path = require('path'); Const filename = path.join(__dirname, files); const filename = path.join(__dirname, files); $require.cache=$require.cache||{}; if($require.cache[filename]) return $require.cache[filename].exports; const dirname=path.dirname(filename); const file = fs.readFileSync(filename); const module = { id:filname, exports: {} }; // exportmodule. exports const {exports} = module; const code = ` (function (module,exports,__dirname,__filename) { ${file} })(module,exports,dirname,filename) `; eval(code); $require.cache[filename]=module; return module.exports; }Copy the code
From the above code we can know:
- The module calls the acquired object after executing it only once
module.exports
It’s all cache even this onejs
Execution is not complete (because the module is added to the cache before it is executed). - Module export is
return
And this variable is essentially thetaa = b
Same thing as assignment,Basic types ofThe exported isvalue.Reference typesThe exported isPointer (memory address). exports
andmodule.exports
Holds the same reference because the last exported ismodule.exports
And so onexports
Performing an assignment causesexports
The operation is no longermodule.exports
The reference.
- The module calls the acquired object after executing it only once
4, require file loading rules
Node uses the CommonJS module specification with the built-in require function for loading module files. The basic function of require is to read and execute a javascript file and then return the exports object of that module. If no specified module is found, an error is reported.
The require loading file rules are as follows:
-
When require loads the JS file, it can omit the extension, or load the JSON file directly
-
By./ or. / start: search for modules from the current file folder according to the relative path
require('.. /file.js'); // require('./file.js'); // require('file.js'); // find file file.js in the same directoryCopy the code
-
Start with / : then look for modules from the system root directory
require('/vue-template/src/main.js'); // Find an absolute pathCopy the code
-
If require passes in a directory path, the package.json file for that directory is automatically viewed and the entry file specified by the main field is loaded
-
If the package.json file does not have a main field, or if there is no package.json file at all, the default is to look for the index.js file in the directory as the module
-
If the argument string does not start with.. / or/indicates that a core module (located in Node’s system installation directory node_modules) is loaded by default
-
The cache file has the highest loading priority, and the system module with the same name has a higher priority than the custom module
-
When Node loads a system module, if there is no node_modules folder in the current folder, it will look up to the project root directory until it is found. If there is no node_modules folder, an error will be reported.
5. ES6 module
-
Distinguish CommonJS modules
-
The CommonJS module prints a copy of the value, the ES6 module prints a reference to the value
-
The CommonJS module outputs a copy of the value, meaning that once a value is output, changes within the module do not affect that value.
// lib.js const counter = 3; const incCounter = () => { counter++; }; module.exports = { counter, incCounter, }; // main.js const mod = require('./lib'); console.log(mod.counter); // 3 mod.incCounter(); console.log(mod.counter); / / 3Copy the code
-
ES6 modules are referenced dynamically and do not cache values. Variables in modules are bound to their modules.
// lib.js export let counter = 3; export const incCounter = () => { counter++; }; // main.js import { counter, incCounter } from './lib'; console.log(counter); // 3 incCounter(); console.log(counter); / / 4Copy the code
When the JS engine statically analyzes a script, it generates a read-only reference to the module load command import. When the script is actually executed, it will be evaluated in the loaded module based on the read-only reference. In other words, as the original value changes, so does the value that the import loads. Here’s another example:
// m1.js export var foo = 'bar'; setTimeout(() => foo = 'baz', 500); // m2.js import {foo} from './m1.js'; console.log(foo); // bar setTimeout(() => console.log(foo), 500); // baz Copy the code
The code above shows that the ES6 module does not cache the results of the run, but instead dynamically removes the loaded module value, and variables are always bound to the module in which they reside.
-
-
The CommonJS module is run time loaded, and the ES6 module is compile time output interface
- The CommonJS module loads an object (i.e
module.exports
Property), the object is generated only after the script runs, and then the related methods are read from the object, which is called loadingRun time loading. - ES6 modules are not objects, but pass
export
The command explicitly specifies the code to output,import
“Takes the form of static commands that are generated during code static parsing rather than loading the entire module, which is calledCompile-time output. This is also very easy to implement in the ES6 moduleTree Shaking
Is an important factor.
- The CommonJS module loads an object (i.e
-
Variables imported using import are read-only and cannot be reassigned.
-
Import is automatically promoted to the top level of the code, with the following code reporting an error:
const num = 100; import xxx from 'xxx-module'; Copy the code
// if for while ... if(boolExp){ import xxx from 'xxx-module'; } Copy the code
Because, CommonJS module is dynamic syntax can be written in the judgment, ES6 module static syntax can only be written at the top level.
-
In the CommonJS module’s top-level scope, this refers to the current module, and in the ES6 module’s top-level scope, this refers to undefined.
-
A circular reference to the CommonJS module
// a.js module.exports.a = 1; var b = require('./b'); console.log(b); module.exports.a = 2; // b.js module.exports.b = 11; var a = require('./a'); console.log(a); module.exports.b = 22; //main.js var a = require('./a'); console.log(a); Copy the code
Run this code with the require demo above to analyze each step of the process:
Nodemain.js -> require(a.js)
, (node
The execution can also be understood as calling the require method, which we omitrequire(main.js)
Content)Enter the require(a) method: check the cache (none) -> initialize a module -> add module to the cache -> execute the contents of module A. js
It should be noted thatThe cache first.After the implementationModule content)A. js: a = 1 -> b: require(b.js)
(A only executes the first line)Enter the same content as 1 -> execute module B. js in require(b)
B.js: line 1 b = 11 -> line 2 require(a.js)
Require (a) this is the second call to require -> judge cache -> cachedModule.exports -> return to b.js
(because thejs
Object reference problem at this timecachedModule.exports = { a: 1 }
)B. js: output {a: 1} -> modify the fourth line B = 22 -> Return to A. js after execution
{b: 22} -> a = 2 -> return to main.js
Main.js: get a -> second line of output {a: 2} -> Execute complete
.
-
A circular reference to the CommonJS module
// bar.js import { foo } from './foo'; console.log(foo); export const bar = 'bar'; // foo.js import { bar } from './bar'; console.log(bar); export const foo = 'foo'; // main.js import { bar } from './bar'; console.log(bar); Copy the code
-
Go to main.js -> import bar.js
-
Bar.js -> import foo.js
-
Foo.js -> import bar.js -> bar.js has already been executed (it thinks the interface already exists, so it will not be executed again) -> output bar -> bar is not defined, bar is not defined We can use function to solve this problem:
// bar.js import { foo } from './foo'; console.log(foo()); export function bar(){ return 'bar'; } // foo.js import { bar } from './bar'; console.log(bar()); export function foo(){ return 'foo'; } // main.js import { bar } from './bar'; console.log(bar()); Copy the code
This is because functions are promoted, bar is already defined when import {foo} from ‘./foo’ is executed, so foo.js loads without error. This also means that if you rewrite function foo as a function expression, you will also get an error.
-
-
Load Node
Node has trouble handling ES6 modules because it has its own CommonJS module format, which is incompatible with ES6 module formats. The current solution is to separate the two and use separate loading schemes for ES6 modules and CommonJS modules. Starting with v13.2, ES6 module support is enabled by default. Prior to this version, to use ES6 modules in Node, add –experimental-modules, such as Node –experimental-modules./index.mjs.
Node requires ES6 modules to use. MJS file names. That is, when Node encounters an.mjs file, it considers it to be an ES6 module, and strict mode is enabled by default. You don’t have to specify “use strict” at the top of every module file.
If you don’t want to change the suffix to.mjs, you can specify the type field as module in your project’s package.json file. If you don’t want to change the suffix to.mjs, you can specify the type field as module in your project’s package.json file. Once set, JS scripts in this directory are interpreted as ES6 Modules.
{"type": "module" // Enable ES6 module mode}Copy the code
If you want to use the CommonJS module at this point, you need to change the CommonJS module scripts to.cjs. If there is no type field, or if the type field is CommonJS, the.js script will be interpreted as a CommonJS module.
1. Main and exports fields
The package.json file has two fields to specify the module’s entry file: main and exports.
-
The main field is relatively simple module, can only use the main field, specify the module load entry file.
{ "type": "module", "main": "./index.js" } Copy the code
The code above specifies that the entry script for the project is./index.js in the ES6 module format. If there is no type field, index.js is interpreted as a CommonJS module.
-
Exports field Exports field has a higher priority than main field. It can be used in many ways. The exports field of the package.json file can specify the alias of a script or subdirectory.
/ / package. Json {" exports ": {". / XXX - file - name" : ". / dir/XXX XXX. Js ", alias / / specified script ". - dir - name / / XXX ": "./xxx-dir/", // specify a subdirectory alias}} // import module1 from 'project-name/xxx-file-name'; import module2 from 'project-name/xxx-dir-name/xxx.js';Copy the code
(2) The name of the exports field is., which is the main entry of the module and has a higher priority than that of the exports field.
{" exports ": {"." : ". / main. Js "}} / / equivalent to {" exports ":". / main. Js "}Copy the code
Because the Exports field is only known by es6-supporting nodes, it can be used to support older versions of Nodes.
{ "main": "./old-version.js", "exports": { ".": "./new-version.js" } } Copy the code
(3) Conditional loading utilization. This alias can specify different entries for ES6 modules and CommonJS modules. Currently, this feature requires the — experimental-Conditional-exports flag to be turned on when Node is running.
{"type": "module", "exports": {".": {"require": "./main.cjs", // require specifies the CommonJS module entry "default": "./main.js" // default specifies the entry for other cases, i.e., the entry for ES6 modules}}}Copy the code
The above can be abbreviated as follows:
{ "exports": { "require": "./main.cjs", "default": "./main.js" } } Copy the code
If there is an alias, it can only be as follows:
{ "exports": { ".": { "./xxx-file-name": "./xxx-dir/xxx.js", "require": "./main.cjs", "default": "./main.js" } } } Copy the code
2. ES6 module loads CommonJS module
With the conditional loading from the previous section, Node itself can handle both modules at the same time.
{
"type": "module",
"main": "./index.cjs",
"exports": {
"require": "./index.cjs",
"default": "./wrapper.mjs"
}
}
Copy the code
Note that the import command loads the CommonJS module as a whole, not as a single output item.
// Import packageMain from 'commonjs-package'; Import {method} from 'commonjs-package';Copy the code
3, CommonJS module load ES6 module
ES6 modules cannot be loaded using the CommonJS require command, which can only be loaded using import().
4. Built-in module of Node
Node’s built-in modules can be loaded as a whole or as specified output items.