Modular development

Modularity can be said to be the front one of the most important front-end development paradigm, with the increasingly complex of the front-end application, project code have been expanded to have to spend a lot of time to management level, and the modular organization is one of the most mainstream code = > he through the complex code according to the different functions are divided into different modules, The word modularity is only an idea, or a theory, and does not contain a concrete implementation, so let’s learn together: How to implement the idea of modularity in front-end projects and some of the current mainstream approaches or tools in the industry:

1.1. Modular evolution process

The early technical standards didn’t anticipate the front-end industry can have today the scale of development, so a lot of in the design of the legacy leads to now go to realize the front-end module will meet a lot of problems, although say now these problems are solved by some standard or some tools, however, he solve the evolution of the process, Let’s take a look at the evolution of implementing modularity at the front end

1.1.1. Method of file division

Modularity in the earliest JavaScript was actually implemented based on file partitioning, which also made the most primitive modular system on the Web. State that are relevant to each function, and he alone in different files, data agreed each file is a separate module = > to use the module, is introduced into the module page, a script tag corresponds to a module, called directly in the code module in the global member (the member may be a variable, It could be a function.

// Module B: status data and functions related to module B
var name = 'module-b';
function method1 () {
  console.log(name + '#method1');
}
function method2 () {
  console.log(name + '#method2');
}
Copy the code
// The use of modules<body>
  
  <script src="module-b.js"></script>
  <script type="text/javascript">
  	method1();
    name = 'foo';
  </script>
</body>
Copy the code

=> Disadvantages of this general method of planning:

  • Pollution is global: all modules work directly in the global scope, with no independent private space, so that all members of the module can be arbitrarily accessed or modified outside the module;
  • Naming conflict problem: once more modules, it is easy to produce naming conflicts;
  • Unable to manage module dependencies

In general: this way completely depends on the convention, once the project on the volume, not so; However, some problems are exposed in this process. If they can be well solved, modularization can be better realized.

1.1.2 namespace mode

Agreed in this phase, each module only expose a global object, all the members of the module is mounted to the object, under the specific approach: = > on the stage of 1.1.1, wrap each module into a global object means to achieve, a bit like in a module for the module to add some members of the anonymous space feeling;

//module A: Status data and functions related to module A
var moduleA = {
  name: 'module-a'.method1: function () {
  	console.log(this.name + '#method1');
	},
  method2: function () {
    console.log(this.name + '#method2'); }}Copy the code
// The use of modules<body>
  
  <script src="module-a.js"></script>
  <script type="text/javascript">
  	moduleA.method1();
    moduleA.name = 'foo';
  </script>
</body>
Copy the code

This approach reduces the problem of naming collisions, but there is no private space, and members within a module can still be modified externally. Dependencies between modules have also not been resolved;

1.1.3, IIFE

In this phase, the module is provided with private space by executing functions immediately. This is done by placing each member of the module in a private scope provided by a function. The need to expose external members can be mounted to the global object to achieve this;

; (function () {
  var naem = 'module-b';
  function method1 () {
    console.log(name + '#method1');
  }
  function method2 () {
    console.log(name + '#methid2');
  }
  // Expose members to the public
  window.moduleB = {
		method1: method1,
    method2: method2 }; }) ()Copy the code
// The use of modules<body>
  
  <script src="module-b.js"></script>
  <script type="text/javascript">
  	moduleB.method1();
    // Private members of the module cannot be accessed
    console.log(methodB.name); // undefined
  </script>
</body>
Copy the code

In this way, the concept of private members is realized. Private members can only be accessed by the closure of the internal members of the module, and cannot be used externally. In this way, the security of private variables is ensured.

You can also use arguments from self-executing functions as dependency declarations, making the direct dependencies of each module more obvious. For example, if you rely on jquery in the following example, you can use a self-executing function to take a jquery argument and pass the jquery argument when called immediately.

; (function ($) {
  var name = 'module-a';
  function method1 () {
    console.log(name + '#method1');
    $('body').animate({ margin: '200px' });
  }
  function method2 () {
    console.log(name + '#method2');
  }
  window.moduleA = {
    method1: method1,
    method2: method2
  };
})(jQuery)
Copy the code

This makes it clear when you maintain the module later that it relies on jquery;

So that was the early approach to modularity without tools and specifications that solved all sorts of problems of implementing modules in the front-end domain, but there were still some unanswered questions;

1.2. Modular specification

Or more of these methods is based on the original module system, through the agreed way to realize the modular code organization, these ways in different developers to implement have some subtle differences, in order to unify the different developers and the difference between different projects, you need a standard to regulate the realization of the modular approach; => In addition in the modular for module loading problem, the above several ways are through the script tag to manually introduce the module, which means that the module loading is not controlled by the code, once a long time later, it is very troublesome to maintain; Imagine if you rely on a module in your code and forget to reference that module in your HTML. Or removing a reference to a module from your code and forgetting to remove the reference from your HTML can cause a big problem. => So we need some basic common code to help us load modules through code, which means we now need a modular standard and a module loader;

1.2.1 CommonJs specification

It is a set of standards proposed in nodeJs, in nodeJs all module code must follow the CommonJs specification; CommonJs has the following rules:

  • A file is a module;
  • Each module has a separate scope;
  • Exports members through module.exports
  • Load the module via the require function;

If this specification is used on the browser side, there are some problems: CommonJs loads modules in synchronous mode (because node’s execution mechanism is to load modules at startup, there is no need to load modules during execution, only modules are used), which leads to inefficient pages (a large number of synchronous mode requests occur each time the page loads). So in the early modular front-end, we did not choose CommonJs specification, but redesigned a specification for the browser side with the characteristics of the browser: AMD

1.2.1 AMD specification

AMD(Asynchronous Module Definition – Asynchronous Module specification), => also released a very famous library: require.js, which is based on the AMD specification and is also a powerful Module loader;

In AMD, each module needs to be defined by the define() function

// Define a module
//define: accepts two arguments by default or three;
// Pass three arguments: the first argument is the name of the module (to be used later when the module is loaded); The second argument is an array that declares the module's dependencies. The third parameter is a function whose parameters correspond to the second parameter one by one. Each parameter is the exported member of the dependency. The function provides the module with private space
define('module1'['jquery'.'./module2'].function ($, module2) {
  return {
    start: function () {$('body').animate({ margin: '200px' })
      module2()
    }
  }
})
Copy the code
// Load a module // use and define function types. The require function is only used to load modules. Once RequireJs loads a module, it automatically creates a script tag to send the corresponding script file request and execute the corresponding module code. require(['./module1'], function (module1) { module.start()})
Copy the code

At present, most third-party libraries support AMD specifications, that is to say, THE AMD ecosystem is better, but AMD has the following disadvantages:

  • It’s relatively complicated to use

    In the process of code writing, a lot of code of define and require operation module is also needed to deal with the business code, which leads to a certain increase in the complexity of the code.

  • Module JS file requests are frequent

    When the module division in the project is too detailed, the number of requests for JS files in the same page will be particularly large, resulting in low efficiency of the page;

1.2.3, sea-.js + CMD

AMD was introduced at the same time, implementing the CMD standard: The standard of this module is somewhat similar to COmmonJs and is somewhat similar to RequireJs in use, but the idea is to make our code as similar as possible to COmmonJs to reduce the learning cost of developers; However, this approach has since been compatible with RequireJs;

//CMD define(function (require, exports,) Module) {var $= require('jquery') // exports or module.exports = function () { console.log('module2 ~') $('body).append("

module2

") }})
Copy the code

P.S. These histories are especially important to friends who only touch the front in peacetime;

1.2.4 Modular standard specifications

Although all of the above standards are modularized, they all have some problems that developers find difficult to accept; => With the development of technology, JavaScript standards are gradually improved, and in the modular implementation of the way relative to the past has changed a lot, the stage of the modular has been very mature => at present, for the front-end modular implementation of the best way are basically unified: ** In the Node environment, follow the CommonJs specification to organize Modules, in the browser environment, use ES Modules specification; ** of course, there are few sectoral exceptions;

For ES Modules, it is the latest module system defined in ECMAScript 2015(ES6), which means it is only developed in recent years, so there will be compatibility issues. At the time ESModules was introduced, none of the major browsers supported the standard; With the popularity of a number of packaging tools such as WebPack, this specification gradually became popular;

So far, ES Modules can be said to be the most mainstream front-end modular specification, compared with AMD specifications, ES Modules in the language level to achieve modular, more perfect; ES Modules is already supported by most browsers today

1.3, ES Modules

Learning ESModules can be started from two aspects: first, it is necessary to know what features and syntax they agree on as a specification or standard; Secondly, how to solve the problem of compatibility in the operating environment through some tools or solutions;

1.3.1, ES Modules Basic features

1.3.1.1. By adding type = module to a script, you can execute js code using ES Modules standards.
<body>    <! -- Add type = module to a script to execute js code using ES Modules standards -->  <script type="module">  	console.log('this is es modules');  </script></body>
Copy the code

Run the above page and print this is es Modules on the console panel, meaning that script can still be executed as JS if type is set to module. It’s just that there are some new features relative to normal script tags;

1.3.1.2 The strict mode is automatically adopted in ES Modules, ignoring ‘use strict’ to declare strict mode: it is also strict if ‘use strict’ is not added
<body>    <script type="module">  	// Strict mode representation: this console.log(this) cannot be used in global mode;</script></body>
Copy the code

Execute the code above: print undefined

<body>    <script type="text/javascript">  	// Strict mode representation: this console.log(this) cannot be used in global mode;</script></body>
Copy the code

Execute the code above: Print out the window

1.3.1.3. Each ES Modules runs in a separate private scope
<body>    <script type="module">  	var foo = 100    console.log(foo)  </script>  <script type="module">  	console.log(foo)  </script></body>
Copy the code

Run the code above: print 100 foo is not defined; Each ES Modules has a private scope, which eliminates global contamination of variables.

1.3.1.4 ES Module requests external Js modules through CORS

If the Js module is not under the same origin address, it needs to request the server address in the response header must provide a valid CORS header;

<body>  <script type="module" src="https://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script></body>
Copy the code

The link above does not support CORS, so a cross-domain error is printed and the request is aborted in the Network.

<body>  <script type="module" src="https://unpkg.com/[email protected]/dist/jquery.min.js"></script></body>
Copy the code

The link above is CORS enabled, so requesting jquery is fine; CORS does not support file access, must use HTTP server form to make the page work;

1.3.1.5, the script tag of ES Modules automatically delays the execution of the script;

This is equivalent to the defer attribute of the script tag, and adding type=module is equivalent to adding the defer attribute; => The loading process of the web page adopts the mechanism of immediate execution for script tags, and the page rendering will wait for the completion of script execution before continuing to render;

<body>  <script type="text/javascript">  	alert('hello')  </script>  <p>What you want to display</p></body>
Copy the code

Run the above file, the pop-up dialog box time page needs to display the content has not been presented; => Because the script is executing now; Click OK to display the content after the script is complete.

<body>  <script type="module">  	alert('hello')  </script>  <p>What you want to display</p></body>
Copy the code

Run the above file, when the pop-up dialog box needs to display the content has been displayed;

<body>  <script defer>  	alert('hello')  </script>  <p>What you want to display</p></body>
Copy the code

Run the above code, pop-up window when the need to display the content has been displayed;

1.3.2 ES Modules export

ES Modules import and export Modules. => These two functions are mainly constituted by export and import. Export: exposes interfaces in a module, and import is to import interfaces provided by other modules into a module.

App.js index.html module.js => Load app.js as a module in index.html, try to load module.js exposed module members in app.js;

//index.html<body>  <script type="module" src="app.js"></script></body>
Copy the code

In ES Modules, each module runs in an independent private scope. All members of the module cannot be directly accessed externally. Some members need to be provided externally. =>export can modify not only variables but also functions: export function XXXX: this function can also be used as an exported member of the module; => Modules export class XXXX => ES Modules Export {XXX, XXX, XXX} => in the tail of the module is more common, because the way to export the module in the tail of the module is more intuitive to describe which members the module provides to the outside, so it is easier to understand the code. Export {XXX as XXX} => There is a special case when you rename the exported member: If you rename the exported member to default, the exported member will use the module’s default exported member => in ES Modules. The export default variable => is used as the module’s default export;

//module.js// export with a single declaration member /* export var name = 'foo modules'export function hello () {console.log('hello')}export class Var name = 'foo modules'function hello () {console.log('hello ES modules')}class Person {}//export {name, hello, Person}// Rename exported members as as //export {name as fooName, hello, Person}// When the exported members are renamed as default, The default member must be renamed when introduced (default is the keyword). export { /* name as default, */ hello as fooHello }export default name
Copy the code

Load exposed module members externally via import keywords;

//app.js//import {name} from './module.js'// When the exported member is renamed, //import {fooName} from './module.js'// When the exported member is renamed to default, the imported member must be renamed to default (default is the keyword); //import {default as fooName} from './module.js'// The default export can accept import fooName from directly with a variable name './module.js'console.log(fooName)
Copy the code

The module system will automatically request the module.js module when working, and use the exposed members as local variables;

Run index.html, and the console prints: foo Modules

1.3.2.1, ES Modules Import and export precautions
Export {XXX, XXXX} export {XXX, XXXX}

The curly braces ({}) followed by export is a fixed syntax, not an object literal. To export an object, use export default {XXX, In this case, the curly braces ({}) following default are literal objects, which is completely different from export {}. Export default can be followed by a variable or value. The export default {} curly braces will be interpreted as an object by JS;

1.3.2.1.2: import {XXX, XXXX}

ES Modules introduces variables with import {}, which is not a destructively assigned value, but a fixed use of ES Modules to extract exported members from Modules.

1.3.2.1.3, ES Modules export a reference to the exported member

Take var name = ‘foo’ as an example, the variable name is defined in the module, which can be understood as the value foo is stored in the space of name in the memory. The name is exported by export, and the name is raised in other JS. The variable after lifting does not copy a copy of name to other JS, but encodes the memory space of name to other JS, so accessing name in other JS is always accessing the name space defined in the module.

//module.jsvar name = 'foo modules'var age = 18setTimeout(function () { name = 'bar modules' }, 1000)export { name, age }
Copy the code
//app.jsimport { name, age } from './modules.js'console.log(name, age)setTimeout(function () { console.log(name, age)}, 1500).
Copy the code

To execute the file, print out Foo Modules 18 first, then bar Modules 18 1.5 seconds later

1.3.2.1.4, ES Modules Exposed members are read-only

ES Modules expose references to exposed members. Exposed references are read-only, meaning that exposed members cannot be modified outside the module.

//module.jsvar name = 'jack'var age = 18export { name, age }
Copy the code
//app.jsimport { name, age } from './module.js'console.log(name, age)name = 'tom'setTimeout(function () { console.log(name, age)}, 1000)
Copy the code

Assignment to constant variable => Assignment to constant variable So the extracted member is a constant;

Some constant modules can be defined based on this feature: for example, configuration files in a project, which can only be read externally but cannot be modified;

1.3.3, ES Modules import

1.3.3.1 import Indicates the path after from when importing modules

Import Import module path followed by from when importing modules. The path is a string. This string must use the full file name and cannot omit the.js extension. Do not write only to the parent directory of index.js => When using the packaging tool to package modules, you can omit the extension name and the operation of the default file of index.js. If the relative path is used, the./ in the relative path cannot be omitted. If the relative path is omitted, the path starts with a letter, and the ES Modules parse it to load third-party Modules. You can also use absolute paths starting with /; => This path can also use the complete URL to load the module, which requires the complete URL address after the FROM, which also means that the module file on the CDN can be directly referenced.

1.3.3.2. Execute the module without extracting module members

To execute a module without lifting members of the module, leave {} after import empty:

import {} from './module.js'
Copy the code

So this is shorthand for theta

import './module.js'
Copy the code

This feature is useful for guiding subfunctional modules that do not require external control;

1.3.3.3 There are many exported members of the module

Module export members are many, and in the import is to use the exported members, then you can use the * way to extract all the members, extracted and renamed in an object;

import * as mod from './modules.js
Copy the code

Use the member with: mod. Member name;

1.3.3.4 Dynamic import of modules

The import keyword can be understood as the declaration of importing modules. The path of importing modules needs to be clear in the development stage. However, the import module cannot be used in the following two minutes: 1. The module’s path is known at run time, when you cannot use import from a variable; 2. The import module must meet certain conditions before it can be imported. In this case, import cannot be used, because import keywords can only appear at the top level and cannot be nested in the judgment of if

This is where you need to import modules dynamically: use the import function to import modules dynamically

import('./module.js')
Copy the code

The import() function can be called anywhere, and it returns a Promise that the module will be loaded automatically when the module is finished loading

import('./module.js').then(function (module) {  console.log(module)})
Copy the code
1.3.3.5. References to both named and default members are exported

When a module exports both named and default members, the named members can be imported normally using {}, and the default members can be imported using as rename.

import { name, age, default as title } from './module.js'
Copy the code

This can be abbreviated as:

import title, { name, age } from './module.js'
Copy the code

1.3.4 ES Module Exports and imports members

In addition to importing modules, import can also be used with export. The effect is to directly import the result as the export member of the current module: Import {XXX, XXXX} fromt ‘./ XXXX ‘to export {XXX, XXXX} from’./ XXXX ‘

/* import { foo, bar } from './module.js' */export { foo, bar } from './module.js'console.log(foo, bar);
Copy the code

ReferenceError: foo is defined; Members cannot be accessed in the current scope after modification;

This feature is often used when writing index.js: The modules scattered in each directory are grouped together and exported through the index file for external use.
1.3.4.1, sample

App.js index.html module.js components/avatar.js components/button.js component/index.js

//index.html<body>  <script type="module" src="app.js"></script></body>
Copy the code
//app.jsimport { foo, bar } from './module.js'import { Avatar, Botton } from './component/index.js'
Copy the code
//module.jsvar foo = 'foo name', bar = 'bar name'export { foo, bar }
Copy the code
//avatar.jsexport var Avatar = 'Avatar Component'
Copy the code
//button.jsexport var Button = 'Buttom Component'
Copy the code
//index.jsexport { Avatar } from './avatar.js'export { Button} from './button.js'
Copy the code

If the default member is exported from the component, extract the default member and rename it by as.

//button.jsvar Button = 'Button Component'export default Button
Copy the code
//index.jsexport { default as Button } from './button.js'export { Avatar } from './avatar.js'
Copy the code

1.3.5, ES Modules Browser environment Polyfill (solve the compatibility problems caused by the runtime environment)

ES Modules was introduced in 2014, which meant that earlier browsers couldn’t possibly support this feature; Up to now: IE and some domestic browsers do not support this feature, so you need to consider compatibility problems when using ES Modules;

Example directory structure: index.html module.js

//index.html<body>  <script type="module">  	import { foo } from  './module.js'    console.log(foo)  </script></body>
Copy the code
//module.jsexport var foo = 'foo name'
Copy the code

Print foo name in Chrome; There is no output in IE; Because IE is not compatible with ES Modules; If you are familiar with common compilation tools, you can use some common compilation tools to compile ES6 code into ES5 during development and then execute it. => Here is a polyfill that lets the browser directly support ES Modules: browser-es-module-loader.js => this is a NPM module: For NPM module, you can get all JS files under the module through the CDN service of unpkg.com. Find the babel-brow-build.js and browser-es-module-loader.js files and import them into HTML; => unpkg.com/browser-es-module-loader => unpkg.com/browser-es-module-loader@ XXX /dist/ => add babel-browser-build.js and dist, respectively Browser-es-module-loader. js link addresses are imported into the page separately;

//index.html<body>  <script src="https://unpkg.com/[email protected]/dist/babel-browser-build.js"></script>  <script src="https://unpkg.com/[email protected]/dist/browser-es-module-loader.js"></script>  <script type="module">  	import { foo } from './module.js'    console.log(foo)  </script></body>
Copy the code

The first is not browser-es-module-loader core code. The second is browser-es-module-Loader core code. When working, the code is read out by es-module-loader and handed to Babel for conversion, so that the code can work properly; => Then run the code in IE: Error => Promise not defined, this is because THE ES6 features are not supported in IE, so you need to introduce Promise’s polyfill => find the link via unpkg.com/promise-polyfill and introduce it to the page

//index.html<body>  /*amend + */  <script src="https://unpkg.com/[email protected]/dist/polyfill.min.js"></script>  /*amend end */</body>
Copy the code

Re-execute index. HTML in IE and print foo name; At this point, es-Module in IE already works properly; => Working principle: the browser is not compatible with the syntax to Babel to convert, for the need to import files, through Ajax to request, request back code through Babel conversion, so as to solve the compatibility problem under IE; => Now there is a problem: in browsers that support ES Module, Module foo is executed twice because the browser itself executes once and es-module-loader executes once; This problem can be solved with a new attribute of script: nomodule. If the nomodule attribute is added to the script tag, the script will only be executed in browsers that do not support ES Modules.

//index.html<body>  /*amend c */  <script nomodule src="https://unpkg.com/[email protected]/dist/polyfill.min.js"></script>  <script nomodule src="https://unpkg.com/[email protected]/dist/babel-browser-build.js"></script>  <script nomodule src="https://unpkg.com/[email protected]/dist/browser-es-module-loader.js"></script>  /*amend end */</body>
Copy the code

This ES Modules-compatible approach is only suitable for local testing, i.e. development; Do not use this in a production environment, because its principle is to dynamically parse scripts at runtime, which is very inefficient; In a production environment, the code should be pre-compiled so that it works directly in the browser.

1.3.6 ES Module in Node.js Support

ES Modules, as the modularity standard of JavaScript language, gradually unify all the modularity requirements of JavaScript. NodeJs, as a very important application area of JavaScript, has gradually begun to support ES Modules. Since nodeJs 8.5, internal support for ES Modules has been experimental features, namely: You can write code natively using ES Modules in nodeJs;

Considering that there is a large gap between the original CommonJs specification and ES Modules specification, ES Modules is still in a transitional state. Next, we’ll use ES Modules in Node. Note that node must be older than 8.5

Project directory structure: index.js module.js

//index.jsimport { foo, bar } from './module.js'console.log(foo, bar)
Copy the code
//module.jsexport const foo = 'hello'export const bar = 'world'
Copy the code

To use ES Modules directly in Node, change the file extension to.mjs: After the modification, the directory structure is => index.mjs module. MJS. During the modification, the system will prompt you whether to modify the import path

//index.mjsimport { foo, bar } from './module.mjs'console.log(foo, bar)
Copy the code

To run index.js, add: –experimental-modules => node –experimental-modules index. MJS => will print Hello world => a warning, because ES modules is a production feature, Hopefully not used in production environments;

1.3.6.1, ES Modules Support for Node native Modules

Import node’s native Modules via ES Modules

//index.mjs/*amend + */import fs from 'fs'fs.writeFileSync('./foo.txt', 'foo Module')
Copy the code

TXT. Foo. TXT contains foo Module. This means that you can load native Modules in Node via ES Modules.

1.3.6.2, ES Modules Member extraction of Node native Modules

Editing the MJS

/*amend + */import { writeFileSync } from 'fs'writeFileSync('bar.txt'.'bar Module')
Copy the code

TXT (bar module), ES Modules can extract members from Node native Modules. Node native module, Node is compatible, Node will be the member of the native module to export separately, also will export an object;

1.3.6.3 ES Modules Support for third-party Node Modules

Install lodash: CNPM I lodash -d and write index.mjs

//index.mjs/*amend + */import _ from 'lodash'console.log(_.camelCase('ES Module'))
Copy the code

If you execute index. MJS, it will print: esModules, indicating that ES Modules can be loaded into third-party Modules.

1.3.6.4, ES Modules Extract members of Node third-party Modules

Editing the MJS

//index.mjs/*amend + */import { camelCase } from 'lodash'console.log(camelCase('ES Module Working'))
Copy the code

Error: index. MJS The Requested module ‘Lodash’ does not provide an export named ‘camelCase’ {} after import is not a structural statement, and third-party modules are exported objects. This object is exported as the default member of the module.

You cannot extract third-party module members in this way, you must use the default import to extract members;

ES Modules in Node.js interact with CommonJS Modules

Learn how to load CommonJs Modules in ES Modules. => Directory structure: commonjs.js esmodule.mjs

1.3.7.1 You can import CommonJs Modules in ES Modules
//commonJs.jsmodule.exports = { foo: 'commonJs exports value'}
Copy the code
//esModules.mjsimport mod from './commonJs.js'console.log(mod)
Copy the code

Esmodules.mjs => output: {foo: ‘commonJs exports value’} => means commonJs Modules can be imported from ES Modules

Exports => exports => exports => module.exports;

//commonJs.js// module.exports = {	//foo: 'commonJs exports value'//}exports.foo = 'commonJs exports value'
Copy the code

Run esmodules.mjs => output: commonJs exports value

1.3.7.2 CommonJs can always export only one default member

The CommonJs module can always export only one default member, and you can only extract the default member in ES Modules.

//esModules.mjs// import mod from './commonJs.js'//console.log(mod)import { foo } from './commonJs.js'console.log(foo)
Copy the code

Esmodules.mjs => output: SyntaxError: The requested module ‘./ commonjs.js’ does not provide an export named ‘foo’ => in commonjs.js Exports.foo, which can be extracted from ES Modules => Note that import does not destruct exported objects

1.3.7.3 No ES Modules can be imported into CommonJs
// esmodules.mjs => export const bar = 'es modules export value'
Copy the code
// commonjs.js => const mod = require('./ esmodule.mjs ')console.log(mod)
Copy the code

Commonjs.js => Error: Must use import to load ES Modules => This is not allowed in Node native;

1.3.8 ES Modules in Node.js and CommonJs Modules

Project directory structure: cjs.js esm.mjs

Log (module object console.log(module)// export object alias console.log(exports)// Absolute path of the current file console.log(__filena) // Console. log(__dirname)
Copy the code

Five members are printed according to CommonJs standards. These five members are global members of modules in CommonJs, which can be understood as global variables. In fact, these five members are built into the module;

CJS: node js.js => Prints normally;

//esm.mjs// Prints five members of cjs.js respectively
Copy the code

Run esm. MJS through nodemon: nodemon –experimental-modules esm. MJS => These 5 members cannot be accessed in ES Modules. This means that you can’t use these 5 members in ES Modules. The reason is that these 5 members are provided by parameters after CommonJs wrapped the module as a function. The loading mode of ES Modules has changed, so they are not provided. Module, require, exports can be imported and exported from ES Modules. For –filename and __dirname can be import.meta.url instead;

//esm.mjsconsole.log(import.meta.url)
Copy the code

This URL is the fileURL address of the current working file, => The fileURL can be converted into a path with the help of fileURLToPath in the URL module;

//esm.mjsimport { fileURLToPath } from 'url'const __filename = fileURLToPath(import.mate.url)console.log(__filename)
Copy the code

The resulting __filename path is the same as that in CommonJs;

For dirname, you can use the dirname() method in the path module: this method extracts the folder part of the path;

//esm.mjs/*ament + */import { dirname } from 'path'const __dirname = dirname(__filename)console.log(__dirname)
Copy the code

The resulting __dirname is the same as the path in CommonJs

The new version of ES Modules in Node.js supports ESM

ES Modules are a step forward in the latest version of Node

Project directory structure: index.mjs module.mjs

//module.mjsexport const foo = 'hello'export const bar = 'world'
Copy the code
//index.mjsimport { foo, bar } from './module.mjs'console.log(foo, bar)
Copy the code

In the new version, you can add type to your project’s package.json: Module field, then all JS files under the project work with ES Modules by default, there is no need to change the JS extension to MJS (restore index.mjs to index.js);

If you want to use CommonJs, you need to change the extension of the CommonJs js file to CJS

Jsconst path = require('path')console.log(path.join(__dirname, 'foo')) // Change the extension of common.js to CJS => common. CJS
Copy the code

1.3.10, ES Modules in Node.js

With earlier Versions of Node, you can use Babel to implement ES Modules compatibility. Babel is currently the most popular JS compiler, which can be used to compile code using new features into the current environment compatible code. This section describes using Babel compatible ES Modules on earlier versions of Nodes

Project directory structure: index.js module.js

Install Babel

cnpm i @babel/node @babel/core @babel/preset-env -D
Copy the code

When this is done, you can find the babel-node command. At this point, you can run this command on the command line => run the babel-node command

yarn babel-node
Copy the code
//module.jsexport const foo = 'hello'export const bar = 'world'
Copy the code
//index.jsimport { foo, bar } from './module.js'console.log(foo, bar)
Copy the code

Run index.js through babel-node: yarn babel-node index.js => Output: SyntaxError: Unexpecte token import(import is not supported)

Reason: Because Babel is implemented based on the plug-in mechanism, the core module does not transform the code, each feature to transform the code is implemented through the plug-in, now need a plug-in to transform each feature of the code;=> perset-env => set of plugins, which contains all the new features of the latest JS standard,Here we use perset-env to convert ES Modules in our code;

Add –presets=@babel/preset-env when running code using babel-node =>

=> This is a hassle to pass in every time => Put the parameter in the configuration file => Add the.babelrc file in the project root directory and edit.babelrc

//.babelrc{ "presets": ["@babel/preset-env"]}
Copy the code

To execute the code, add the presets parameter: yarn babel-node index.js

It is actually a plugin that converts the code, not preset-env, a separate plugin can be used here or the conversion can be implemented => plugin-transform-modules-commonjs is used here

To install the plug-in:

cnpm i @babel/plugin-transform-modules-commonjs -D
Copy the code

Modify. Babelrc

{  "plugins": [ "@babel/plugin-transform-modules-commonjs" ]}
Copy the code

Index. js is executed using babel-node.