当前位置:网站首页>Es module and commonjs learning notes -- ESM and CJS used in nodejs

Es module and commonjs learning notes -- ESM and CJS used in nodejs

2022-07-05 05:07:00 Fierce chicken

stay NodeJS Use in ES6 modular

The current newer version NodeJS Support ESM and CJS , But the default is CJS Specification to analyze JS Code , Use it directly CJS There is no problem , While using ESM Something needs to be done

.mjs file

stay NodeJS of use .mjs The suffix filename indicates that the file is ES6 Modular file , Can be in .mjs Direct use in documents ESM grammar ( Use import/export Instructions ). In execution, including ES6 The script of the module , Because of the difference NodeJS The degree of support of the version is different , It needs to be executed in different ways

v16.4.0  Fully support 
v13.2.0  Experimental support , Omission  --experimental-modules 
 Older versions require explicit use of parameters  --experimental-modules  perform   or   This experimental feature is not even supported 

node --experimental-modules <path/to/file>

Modify the module specification

Can pass modify NodeJS analysis JS Code time module specification , bring ESM Can be directly in .js Use... In the document ( If you don't modify it, you will report an error if you use it directly ). Create a package.json file ( You can execute in the directory to be created npm init -y Generate package.json), And will "type" Set to "module"

// ./package.json
{
    
  "type": "module"
}

//  When not set  type  Or set to  "commonjs"  It uses  CJS  standard 

After the above configuration , If you want to use CJS grammar ( Use module.exports/require()), You need to in .cjs Use... In the document , Otherwise, an undefined error will be reported

When considered ES6 When the module , Will automatically adopt strict mode ( Browser side is the same )

ES6 modular and CommonJS Mutual reference of modules

ESM It is loaded at compile time ( Static loading ), There is a separate parsing phase for module dependencies , The output of the module is the interface ( quote ), Its loading 、 analysis 、 The execution is asynchronous ;CJS Is runtime load ( Dynamic loading ), The module outputs a value ( Module object ), Values are cached , Its loading 、 analysis 、 The execution is synchronous (require() For a synchronous method ).

It can be seen from the above description of the two specifications , ESM and CJS The implementation of specifications is different , If you want to ES6 Introduced in the module CJS Module or in CJS Introduced in the module ES6 modular , You need to follow the following rules

CJS Module loading ES6 modular :

because ES6 For asynchronous loading ,CJS Load for synchronization , Can't be used directly require() To load the ES6 modular , Can only Using the same asynchronous loading and CJS and ESM All supported import() To load the

// import()  Return to one  promise
import('....').then(res => {
    });

(async () => {
    
  await import('....');
}())

ES6 Module loading CJS modular :

ESM Of import Can be loaded directly CJS modular , But it can only be loaded as a whole . because ES6 Modules need to support static code analysis , and CommonJS The output interface of the module is module.exports, It's an object , Cannot be statically analyzed , So it can only be loaded as a whole

import {
     a } from "./foo.cjs"; //  Report errors 

import m from "./foo.cjs";
const {
     a } = m; //  The right way 

require() Can't be in .mjs Use... In the document ( Only use import or import()), Nor can it be used to load .mjs file ( That is, the above cannot be loaded ES6 modular )

Another thing to note , In the use of webpack In packaged projects , In the same file Don't suggest ESM and CJS A mixture of ,babel Translation can make ESM Turn into CJS, This may often lead to some inexplicable mistakes

//  This is not recommended ,
import m from "./foo.js";

module.exports = {
    
  m
}

https://github.com/xiaoxiaojx/blog/issues/27
https://www.tangshuang.net/7686.html

modular ( package ) Load the rules

By default ( Not through package.json Of exports Option to set the alias ),import command Load one that is not node_modules When the module under , Unless it is a system module , Otherwise, the suffix cannot be omitted , The complete file path must be given

// ./module/index.js
import "./foo/index.js";
//  Even if  ./foo/package.json  Set the entrance  "main": "index.js"  Also can not  import  Directly introducing 
import "./foo";

But for the CJS There are no such restrictions

//  Can directly  require  introduce 
require("./foo")

NodeJS Fully supported CJS standard , By default , Its loading rules are as follows

require("./foo/index.js");
//  The detailed file path is provided ( Including file suffix ), If you can't find it, report an error directly 

require("./foo");
/*  Omitted file path or suffix ( It could be  ./foo/index.js  or  ./foo.js), Look for :  Find the same name  JS  file  foo.js;  Find a directory with the same name  foo  Under the  index.js  file ( If  foo  Under the  package.json  It specifies   Entrance file , According to  package.json  Configuration search in );  No error found  */

require("foo");
/*  Provide only one string , Search according to the following rules :  Find the system module with the same name ;  lookup  node_modules  The files under the , First find the same name JS file  foo.js, Then find the directory with the same name  foo  Next   Of  index.js  file ( If  foo  Under the  package.json  Specifies the entry file , According to  package.json  Configuration search in );  No error found  */

Configure the entry file and path alias

Can pass package.json Medium main and exports Configure some settings to facilitate the introduction of modules

  • package.json Medium main
    This is the designated module entry

    /*  Suppose the directory structure is as follows  ---foo |---index.js |---index2.js |---package.json */
    
    //  Suppose no configuration is done now , Then the default entry is  index.js
    require("./foo"); //  amount to  ./foo/index.js
    
    //  Suppose that  package.json  Add  "main": "index2.js", Then the entrance becomes  index2.js
    require("./foo"); //  amount to  ./foo/index2.js
    
  • package.json Medium exports
    exports Has a higher priority than main

    It can be used to set file path alias :

    {
          
      "exports": {
          
        "./foo": "./utils/foo",
      }
    }
    

    As mentioned above ,import The module path introduced by the instruction must be complete , Here, you can simplify the path by configuring the path alias

    {
          
      "exports": {
          
        "./foo": "./foo.js",
      }
    }
    

    At this point, you can directly import "./foo"

    It can be used to set the main entrance main Path alias of :
    use . Indicates the main entrance

    {
          
      "exports": {
          
        ".": "./index.js",
      }
    }
    
    //  If only the main entry configuration can be slightly written as 
    {
          
      "exports": "./index.js"
    }
    

    because exports Fields only support ES6 Of Node.js Only then did I know , So it can be used to be compatible with old versions Node.js

    //  Take advantage of  exports  Priority is greater than  main  Characteristics 
    {
          
      "main": "./index.js",
      "exports": "./index2.js"
    }
    

    You can realize conditional loading :
    Alias setting of the entry file . in , There are also two sub configurations require and default, Assign separately CJS And other entrances ( Include ESM)

    {
          
      "exports": {
          
        ".": {
          
          "require": "./index.cjs", // cjs  entrance 
          "default": "./index.mjs"  // esm  entrance 
        }
      }
    }
    
    //  Same , Here, if only the main entry is configured, the abbreviation can be used 
    {
          
      "exports": {
          
        "require": "./index.cjs", // cjs  entrance 
        "default": "./index.mjs"  // esm  entrance 
      }
    }
    

    This function needs to be in Node.js When it's running , open --experimental-conditional-exports sign

    node --experimental-conditional-exports ....
    

Two formats of modules are supported at the same time

  • Specify different entry files
    Through the above mentioned exports Configuration to realize

    {
          
      "exports": {
          
        ".": {
          
          "require": "./index.cjs", // cjs  entrance 
          "default": "./index.mjs"  // esm  entrance 
        }
      }
    }
    
  • CJS and ESM Reciprocal transformation
    CJS turn ESM:

    //  from  CJS  Import module from module 
    import m from "./foo.cjs";
    
    //  Extract what you need 
    export const bar = m.bar;
    // ....
    

    ESM turn CJS

    //  from  ES6  Import module from module 
    let m = null;
    (async () => {
          
      m = await import("./foo.mjs");
    })()
    
    module.exports = {
          
      ...m
    }
    

Or use in subdirectories package.json Of "type" Configure the specifications used by the current module , Let a directory be CJS Format , Another directory is ESM Format , But after all, it still involves format conversion , The entry file specifies these configurations , For details, please refer to some existing packages , They often support both formats

ES6 Module considerations

  • import The limits of command
    In addition to the above import when The path must be complete ( Include suffix ),import The path of must also be a relative path , Cannot be an absolute path ( The browser can be an absolute path ) Only local modules can be loaded

  • For and browser import The loading rules are the same ,NodeJS Of .mjs File support URL route

    import "./foo.mjs?query=10086"
    

    As long as the parameters of the same script are different , Will be loaded many times , And saved into different caches ( When there are no parameters or the parameters are the same, it is loaded many times and executed only once ). For this reason , As long as the file name contains :%#? Equal special character , It is best to escape these characters

  • Internal variables
    In order to make ES6 The module can be used in both server and browser without any modification ,NodeJS It is stipulated in ES6 Cannot be used in modules NodeJS Some internal variables of

    arguments
    module
    exports
    require
    __dirname
    __filename
    

Reference resources :

https://es6.ruanyifeng.com/#docs/module-loader
https://es6.ruanyifeng.com/#docs/module
https://zhuanlan.zhihu.com/p/337796076
https://www.jianshu.com/p/1e42fcabf039

原网站

版权声明
本文为[Fierce chicken]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/02/202202140623557094.html