module bundlerã®ä½ãæ¹(ECMAScript Modulesç·¨)
ååã®æºåç·¨ã§ã¯ãmodule bundlerãã©ã®ããã«åãã¦ãããã説æãã¾ããã
ä»åã¯ãdynamic import以å¤ã®æä½éã®å®è£ ãå ¥ãã¦ããã¾ãã
å¤æ´ãããã³ã¼ãä¸è¦§ã¯ãã¡ã
- ECMAScript Modules(ESM)ã«ã¤ãã¦
- åºåãããã©ã³ã¿ã¤ã ã³ã¼ã
- ã³ã¼ããæ¸ãæãã
- ãããã«
ECMAScript Modules(ESM)ã«ã¤ãã¦
ãã¦ãå¤ãã®äººããã§ã«ä½¿ã£ã¦ãã以ä¸ã®ãããªæ§æãESMã¨å¼ã°ãããã®ã§ãã
import { version } from 'module'; export const a = 1;
ä»æ§çã®ããã¥ã¡ã³ã
ã¾ããNode.jsã§ã®ESMã®è§£æ±ºæ¹æ³ã¯CJSã¨ã®äºææ§ãä¿ã¤ããã«å¥éç°ãªãã®ã§ããã§ã¯æ±ãã¾ããã
webpackã§ã¯ç¾å¨ãNode.jsã®scope-packageã®å¯¾å¿ä¸ãªã®ã§ãããå°ããå¾ ã¡ä¸ããã
ECMAScript Modules 㨠CommonJS Modulesã®éã
ESMã®ç¹å¾´ã¯ãäºåã«éç解æãè¡ãã¾ãã ããããã¨ä»¥ä¸ã®ãããªã¡ãªãããå¾ããã¾ãã
- ã©ã³ã¿ã¤ã ã§ã·ã³ã¿ãã¯ã¹ã¨ã©ã¼ãçºçãããã¨ãé¿ããã
- ä¸å¿
è¦ãªã³ã¼ããæ¶ã(dead code elimination)ãã¨ã容æã«è¡ãã
- CJSã®tree shaking対å¿ã¯webpackã§ç¾å¨é²è¡ä¸
ESMã¯ãããã¬ãã«ã§å®£è¨ããªãã¨ãããªãã®ã¯ãã®ãããªçç±ãããããã§ãã
ã¾ããCJSã¯åæã§ãããdynamic importã¯éåæã§ãã(require.ensure
é¤ã)
åºåãããã©ã³ã¿ã¤ã ã³ã¼ã
å ã«å®æããã³ã¼ããè¦ã¦ããã¾ãã
// entry.js import { add as addNumber } from './module1.js'; console.log(addNumber(10)); // module1.js export function add(n) { return 10 + n; }
ä¸è¨ã®ã³ã¼ãã¯ä»¥ä¸ã®ãããªåºåã«ãªãã¾ãã
((modules) => { const usedModules = {}; function require(moduleId) { if (usedModules[moduleId]) { return usedModules[moduleId].exports; } const module = (usedModules[moduleId] = { exports: {}, }); modules[moduleId](module, module.exports, require); return module.exports; } require.__defineExports = (exports, exporters) => { Object.entries(exporters).forEach(([key, value]) => { Object.defineProperty(exports, key, { enumerable: true, get: value, }); }); }; return require(0); })({ 0: function (module, exports, require) { const __BUNDLER__1 = require(1); console.log(__BUNDLER__1['add'](10)); }, 1: function (module, exports, require) { function add(n) { return 10 + n; } require.__defineExports(exports, { add: () => add, }); }, });
ä»åã¯CJSã¨ã®äºæããã¾ãèããªãããã__esModule
ãexports
ã«ã¢ãµã¤ã³ã¯ãã¾ããã
ã»ãã®ãã¿ã¼ã³ãè¦ãå ´åã¯ãã¡ã
CJSã¨ESMã®åºåã®éã
ESMãæçµçã«ã¯CJSã«åãããç¶æ
(require
)ã«ãªãã¾ããã大ããªéãã2ç¹ããã¾ãã
ESMã§ã¯ã©ã³ã¿ã¤ã ã³ã¼ãçææã«æ¢ã«å®è¡å ã確å®ããã
å
ç¨ã話ããã¨ããESMã§ã¯éç解æãè¡ããã¨ãåæã¨ãªãããå®è¡åã«ãã¹ã¦ç¢ºå®ããã¾ãã
ãã®ã³ã¼ããCJSã§æ¸ãã¨ä»¥ä¸ã®ãããªåºåã«ãªãã¾ãã
// CJS })({ 0: function (module, exports, require) { const { add: addNumber } = require(1); console.log(addNumber(10)); }, 1: function (module, exports, require) { function add(n) { return 10 + n; } module.exports = { add, }; }, });
CJSã®å ´åã1
ã0
ã«å¼ã³åºããã¦ãã®æã«usedModules[1].exports
ã«add
ãç»é²ããã0
ã§ä½¿ããã¾ãã ãããã0
ããããã¨æ¬å½ã«1
ã«add
ãããã®ãããããªããããå®è¡æã«è½ã¡ãå¯è½æ§ãããã¾ãã
ESMã¯äºåã«å®è¡ãããã®ãäºç´ããã³ã¼ãã«å¤æãããã¨ã«ãããã®åé¡ãé²ãã¾ãã
// ESM })({ 0: function (module, exports, require) { const __BUNDLER__1 = require(1); console.log(__BUNDLER__1['add'](10)); }, 1: function (module, exports, require) { function add(n) { return 10 + n; } require.__defineExports(exports, { add: () => add, }); }, });
__BUNDLER__1['add'](10)
ã®ããã«å¿
ãå®è¡ã§ããå½¢ã§ã³ã¼ããåããã¾ãã
ããå°ããã£ããæ¸ããªãã(0, __BUNDLER__1['add'])(10);
ã¨å¤æããã»ããè¯ãã§ãã ããããªãã¨this
ã®ã¹ã³ã¼ããæ
ä¿ã§ããªãããã§ããæ´ã«ã.add
ã«å¤ããã¨ã³ã¼ãéãæ¸ãã¾ãã(ãããwebpack@3ã§ãã£ã¡ã«ç§»è¡ããè¨æ¶)
ã¤ã¾ããå¼ã³åºãå´ãæ¢ã«å¼ã³åºãããå´ã®å é¨ãææ¡ãã¦ããç¶æ ã¨ãªãå®è¡æã«ã¨ã©ã¼ãèµ·ããã®ãé²ããã¨ãããã¨ã§ãã
å±æ§ã®ä»ä¸ãå¿ è¦ã¨ãªã
æ°ããrequire
ã«__defineExports
ã追å ãã¾ããã
ããã¯ãexportsããããã®ã«å¿
ãenumerable
ãä»ä¸ããªããã°ãããªãããã§ãã
IIFEå ã§å ±éã§ä½¿ãããã«ä»¥ä¸ãå®ç¾©ãã¾ãã
require.__defineExports = (exports, exporters) => { Object.entries(exporters).forEach(([key, value]) => { Object.defineProperty(exports, key, { enumerable: true, get: value, }); }); };
ããã¦ãã¢ã¸ã¥ã¼ã«ãåãã¦å¼ã³åºãããæã«ä»¥ä¸ã®require.__defineExports
ãå®è¡ããusedModules[1].exports
ã«Object.defineProperty
çµç±ã§ããããã£ã追å ãã¾ãã
1: function (module, exports, require) { function add(n) { return 10 + n; } require.__defineExports(exports, { add: () => add, // ãã®ãªãã¸ã§ã¯ãã«ãã®ãã¡ã¤ã«å ã®exportããããã®ã追å ããã¦ãã });
ã³ã¼ããæ¸ãæãã
ASTãå¼ããã人ã¯astexplorerã使ãã¨ä¾¿å©ã§ãã
CJSã§ã¯ã³ã¼ãã®æ¸ãæãã¯require('module')
->require(1)
ã®ããã«moduleIdã®ã¿ã®æ¸ãæãã§ãããESMã§ã¯å¼ã³åºãå
çãç·¨éããå¿
è¦ãããã¾ãã
ESMãCJSã¨ã¢ã¸ã¥ã¼ã«ã®ãããä½æã®å¦çã¯å ±éãªã®ã§ãã³ã¼ãã®èµ°æ»å¦çã ããESMæã«æ°ãã追å ããã¾ããã³ã¼ãç®æ
- CJS: modulesã®ãªã¹ããä½ã ->
require
ã®ä¸èº«ãmoduleIdã«å¤æ´ãã - ESM: modulesã®ãªã¹ããä½ã -> ã½ã¼ã¹ã³ã¼ãããã©ãã¼ã¹ãã ->
require
ã®ä¸èº«ãmoduleIdã«å¤æ´ãã- äºæ®µéç®ã§
require
ã«å¤æããCJSã¨åãå½¢ã«ãªãããä¸æ®µéç®ã¯CJSã¨å ±éã§åã
- äºæ®µéç®ã§
ããã§ã¯ãbabelã使ãASTãèµ°æ»ã以ä¸ã®ãã¨ãéæãã¦ããã¾ãã
import
ãrequire
ã¸å¤æ- (e.g.
import a from 'module'
->const a = require('module')
)
- (e.g.
- å¤é¨ã¢ã¸ã¥ã¼ã«ãã使ã£ã¦ããå¤æ°ãé¢æ°çããã¹ã¦ç½®æãã
- (e.g.
a(1);
->__BUNDLER__1["a"](1)
)
- (e.g.
export
ããã¹ã¦ãããã³ã°ããä¸å¿ è¦ãªã·ã³ã¿ãã¯ã¹ãæ¶ã- (e.g.
export const a = 1
->const a = 1
)
- (e.g.
importãå¤æ´ãã
親ã¯ImportDeclaration
ã¨ãªããtypeã¯ImportDefaultSpecifier
, ImportNamespaceSpecifier
, ImportSpecifier
ã¨ãªãã¾ãã
// ImportDefaultSpecifier import a from 'module'; // ImportNamespaceSpecifier import { a } from 'module'; import * as a from 'module'; // ImportSpecifier import { a as b } from 'module'; import { default as a } from 'module'; // CJSã¨ESMã®äºæããªãã¸
ã´ã¼ã«ã¯require
ã«å¤æ´ããæ ¼ç´å
ã®å¤æ°åã«Idè¾¼ã®ååãä»ä¸ãããã¨ã§ãã
æåã«ã¢ã¸ã¥ã¼ã«ã®ãããã³ã°ããIdãçºè¡ãã¦ããã®ã§ãããå¤æ°åã¸ç´ä»ãã¦ããã¾ãã
import { a } from 'module'
-> const __BUNDLE__1 = require('module')
import
ã¯ãã©ã®typeã§ãconst a = require('b')
ã¨ãªããããã¹ã¦å
±éåã§ãã¾ãã
exportãå¤æ´ãã
export
ã¯ãããããªã±ã¼ã¹(e.g. ã¢ã°ãªã²ã¼ã)ãããã®ã§ãæå°éã®å®è£
ã«ãã¦ãã¾ãã
親ã¯ExportDeclaration
ã¨ãªããtypeã¯ExportDefaultDeclaration
ã¨ExportNamedDeclaration
ã¨ãªãã¾ãã
// ExportDefaultDeclaration // FunctionDeclaration export default function a() {} // Identifier const a = 1; export default a; // ArrowFunctionExpression æ¬å½ã¯classã追å ããªãã¨ãã¡ export default () => {} // ExportNamedDeclaration export const a = 1; // æªå®è£ export { a, b }; export a from 'module';
ã´ã¼ã«ã¯ãexportããããã®ã®ãããã³ã°ã§ãååã¨æ¥ç¶å ãææ¡ã以ä¸ã®ããã«å±éãã¾ãã
function add(n) { return 10 + n; } require.__defineExports(exports, { // åå æ¥ç¶å add: () => add, });
ååãä»ä¸ãã
æåã«exportããããã®ã®ååãåå¾ããªããã°ãªãã¾ããããç¶æ³ã«ãã£ã¦åãæ¹ãç°ãªãã¾ãã
// export default function a() node.declaration.name // function a() {} // export default a; node.declaration.id && node.declaration.id.name // export const a = 1 node.declaration.declarations && node.declaration.declarations[0].id.name
ããã§ååãåããªãã¨ãã¯ãexport default () => {}
ãã¯ã©ã¹ã®å¯è½æ§ãèæ
®ãã¾ãã
ãã®å ´åã¯ãååããã¡ããä»ãã¦ããã¦æ¥ç¶å
ãä½ãã¾ãã(e.g. key: default
, name: __default__
)
export default () => {};
-> const __default = () => {};
ã¨æ¸ãæãã¦ååãä»ãã¾ãã
ä¸è¦ãªã³ã¼ããæ¶ã
Object.defineProperty(exports, key)
ã§å¤ãå±éãã¦ãããããã³ã¼ãå
ã«ä¸è¦ãªã³ã¼ããããã¾ãã
export default function a() {}
->function a() {}
export default a
-> ãã®è¡äºèªä½ãä¸è¦export const a = 1
->const a = 1
exportã«ä»éããã³ã¼ãã¯ã©ã³ã¿ã¤ã ã§ã¯å¿ è¦ãªããªãã¾ãã
importããããã®ãã³ã¼ãå ã§ç´ä»ãç½®æãã
ã³ã¼ãå
ã®importãããã®ã使ã£ã¦ããç®æããã¹ã¦æ¸ãæãã¾ãã
ReferencedIdentifier
ã«å
¥ã£ã¦ãã¦ããã¤è¦ªã®typeãImportDeclaration
ãã¿ã¼ã²ããã§ãã
ã´ã¼ã«ã¯ãimportãããã¢ã¸ã¥ã¼ã«ãå¼ã³åºããã¦ããå ´æã__BUNDLER__{id}[function/variable name]
ã«å¤ãã¦ãããã¨ã§ãã
// before import { foo as bar } from 'module'; console.log(bar(10)); // after const __BUNDLER__1 = require('module'); console.log(__BUNDLER_1['foo'](10));
æåã«ãscope hoistingãèããªãã¨ãããªããã以ä¸ã®å¦çãè¡ãã¹ã³ã¼ããåºå®ãã¾ãã
ãããè¡ããã¨ã«ããimportããããã®ã¨ã³ã¼ãå
ã§ä½¿ããã¦ãããã®ãæ£ããç´ä»ãããã¾ãã
const localName = path.node.name; const localBinding = path.scope.getBinding(localName); const { parent } = localBinding.path; // 親ã確å®ããã
importã¯è¤æ°é£ç¶({a, b, ...}
)ãã¦å
¥ã£ã¦ããã®ã§ã«ã¼ããã確èªãã¦ããã¾ãã
parent.specifiers.forEach(({ type, local, imported }) => {});
ãã®ã«ã¼ãã®ä¸ã§ããããã®exportããããã®ã¨ã³ã¼ãå ã§ä½¿ç¨ããããã®ãç½®æãã¦ãã¾ãã
ããã®local
ã¯ã³ã¼ãå
ã®æ
å ±ã示ããimported
ã¯ã³ã¼ãå¤ã®æ
å ±ãæå³ãã¾ãã
ã¤ã¾ããa as b
ã®å ´åãimported
as local
ã¨ãªãimported
ã¯å
ã®æ
å ±ã辿ãä¸ã§éè¦ãªå½¹å²ãæããã¾ãã
ImportNamespaceSpecifier: import * as foo from 'module'
ä¸çªããã©ãããã§ãã
ã¨ããã®ããdefault
ãçç¥ã§ãããã©ããã¯ãã©ãããã©ã¼ã ã«ä¾åãã¦ããããäºææ§ãèããªãã¨ãããªãããã§ãã(Node.js, Babel, typescript, etc..)
ä¾ãã°ãåºåå
ãmodule.exports = require('./foo')
ã¨ãã代表çãªä¾ã§ãã
ããã§ã®æçµçãªå±éå¼ã¯ä»¥ä¸ã®ããã«ãªãã¾ãã
const __BUNDLER_1 = require(1); console.log(__BUNDLER_1['default']); // importå ã®default console.log(__BUNDLER_1['func']()); // importå ã®named exportãããfunc console.log(__BUNDLER_1); /** * default: xxxx * func: yyyy */
ããããããã¯äºææ§ãå´©ãã¦ããã®ã§å®éã¯ãããªãã¦ã以ä¸ã®ã»ããå®å ¨ã§ãã
const __BUNDLER_1 = require(1); console.log(__BUNDLER_1['default']['default']); // importå ã®default console.log(__BUNDLER_1['default']['func']()); // importå ã®named exportãããfunc console.log(__BUNDLER_1); /** * default: { * default: xxxx, * func: yyyy * }, * func: yyyy */ // esModuleã®å ´åã¯getDefaultã使ããå¤å¥ããé¢æ°ãIIFEã«ä½æãã const getDefaultExport = function (module) { const getter = module && module.__esModule ? function getDefault() { return module['default']; } : function getModuleExports() { return module; }; Object.defineProperty(getter, 'esm', { enumerable: true, get: getter }); return getter; };
ãã®ãµã³ãã«ã§ã¯default
ã1é層æã¾ãã«ãã£ã¦ããããObjectã®keyãç´ã§ååã¨ãªãã¾ãã
ImportDefaultSpecifier: import foo from 'module'
defaultã¯å¿
ãä¸ã¤ããç¡ããããnameã¯default
ã¨ãªãã¾ãã
ImportSpecifier: import { foo } from 'module'
æ®éã®named importã§ããã°ãnameã¯importå
ã¨åãã§ãã
åé¡ã¯ããªãã¼ã ããã¦ããå ´åã§ãããã®å ´åã¯ãimported.name
ãè¦ãå¿
è¦ãããã¾ãã
ããã¦ããã¼ã«ã«ã®ãªãã¼ã ããããã®ã¯å
¨é¨ç½®ãæãã¾ãã
// import { foo as bar } from 'module'; const __BUNDLER__1 = require('module'); console.log(__BUNDLER_1['foo'](10)); // <--- barã¯ãããããªãã®ã§fooã«æ»ãã¦ããã
ç½®æãè¡ã
ããããã®ç¶æ ã§ãnameãåããã®ã§æå¾ã«ç½®æãè¡ãã¾ãã
const assignment = t.identifier(`${prefix}${moduleId}${name ? `[${JSON.stringify(name)}]` : ''}`); // ImportNamespaceSpecifierã¯ä»ådefaultãçããã®ã§ä»¥ä¸ // replace `['foo'].foo` with `['foo']` path.parentPath.replaceWith(assignment); // ãã®ä» path.replaceWith(assignment);
ããã§ã®nodeã®åºéã¯ä»¥ä¸ã®ç®æã§ãã
// parent node: | ãã | // current node: | ãã®åºé | ããã¯ã¨ãªã | foo() __BUNDLER_1['foo']()
ãªãImportNamespaceSpecifier
ã§ã¯parentPath.replaceWith
ã使ã£ã¦ãããã¨ããã¨()
é¨åãä¿®æ£ãããã£ãããã§ãã
ããã§ã³ã¼ãç½®æãè¡ããã®ã§ãæåã«åºããã©ã³ã¿ã¤ã ã³ã¼ããä½æããã¾ãã
ãããã«
以ä¸ã§ãmodule bundlerã§ESMã®å¯¾å¿ãããæ¹æ³ã®ç´¹ä»ã¯çµããã§ãã
ãã¹ã¦ã®ã³ã¼ãã¯ãã®PRãè¦ãã¨ãããã¾ãã
æ¬å½ã¯ããã§tree shakingããããã¨æã£ããã ãã©ãäºæ³ä»¥ä¸ã«éãå¤ããªã£ã¦ãã¾ã£ãã®ã§æ¬¡åããã¾ãã
ããä½ãèããããã¨ãã£ãããTwitterã¾ã§ã