很多 react 使用者在从 JS 迁移到 TS 时,可能会遇到这样一个问题:
JS 引入 react 是这样的:
// js
import React from 'react'
而 TS 却是这样的:
// ts
import * as React from 'react'
如果直接在 TS 里改成 JS 一样的写法,在安装了 @types/react 的情况下,编辑器会抛出一个错误:此模块是使用 "export =" 声明的,在使用 "esModuleInterop" 标志时只能与默认导入一起使用。
根据提示,在 tsconfig.json 中设置 compilerOptions.esModuleInterop 为 true,报错就消失了。
要搞清楚这个问题的原因,首先需要知道 JS 的模块系统。常用的 JS 的模块系统有三个:
(AMD 现在用得比较少了,故忽略掉)
babel、TS 等编译器更加偏爱 cjs。默认情况下,代码里写的 esm 都会被 babel、TS 转成 cjs。这个原因我推测有以下几点:
回到上面那个问题。打开 react 库的 index.js:
可以看到 react 是基于 cjs的,相当于:
module.exports = {
Children: Children,
Component: Component
}
而在 index.ts 中,写一段
import React from "react";
console.log(React);
默认情况下,经过 tsc 编译后的代码为:
"use strict";
exports.__esModule = true;
var react_1 = require("react");
console.log(react_1["default"]);
显然,打印出来的结果为 undefined,因为 react 的 module.exports 中根本就没有 default 和这个属性。所以后续获取 React.createElement、React.Component 自然都会报错。
这个问题引申出来的问题其实是,目前已有的大量的第三方库大多都是用 UMD / cjs 写的(或者说,使用的是他们编译之后的产物,而编译之后的产物一般都为 cjs),但现在前端代码基本上都是用 esm 来写,所以 esm 与 cjs 需要一套规则来兼容。
esm 导入 esm
esm 导入 cjs
cjs 导入 cjs
TS 对于 import 变量的转译规则为:
// before
import React from 'react';
console.log(React)
// after
var React = require('react');
console.log(React['default'])
// before
import { Component } from 'react';
console.log(Component);
// after
var React = require('react');
console.log(React.Component)
// before
import * as React from 'react';
console.log(React);
// after
var React = require('react');
console.log(React);
可以看到:
TS、babel 对 export 变量的转译规则为:(代码经过简化)
// before
export const name = "esm";
export default {
name: "esm default",
};
// after
exports.__esModule = true;
exports.name = "esm";
exports["default"] = {
name: "esm default"
}
可以看到:
回到标题上,esModuleInterop 这个属性默认为 false。改成 true 之后,TS 对于 import 的转译规则会发生一些变化(export 的规则不会变):
// before
import React from 'react';
console.log(React);
// after 代码经过简化
var react = __importDefault(require('react'));
console.log(react['default']);
// before
import {Component} from 'react';
console.log(Component);
// after 代码经过简化
var react = require('react');
console.log(react.Component);
// before
import * as React from 'react';
console.log(React);
// after 代码经过简化
var react = _importStar(require('react'));
console.log(react);
可以看到,对于默认导入和 namespace(*)导入,TS 使用了两个 helper 函数来帮忙
// 代码经过简化
var __importDefault = function (mod) {
return mod && mod.__esModule ? mod : { default: mod };
};
var __importStar = function (mod) {
if (mod && mod.__esModule) {
return mod;
}
var result = {};
for (var k in mod) {
if (k !== "default" && mod.hasOwnProperty(k)) {
result[k] = mod[k]
}
}
result["default"] = mod;
return result;
};
首先看__importDefault。它做的事情是:
比如上面的
import React from 'react';
// ------
console.log(React);
编译后再层层翻译:
// TS 编译
const React = __importDefault(require('react'));
// 翻译 require
const React = __importDefault( { Children: Children, Component: Component } );
// 翻译 __importDefault
const React = { default: { Children: Children, Component: Component } };
// -------
// 读取 React:
console.log(React.default);
// 最后一步翻译:
console.log({ Children: Children, Component: Component })
这样就成功获取了 react 模块的 modue.exports。
再看 __importStar。它做的事情是:
(类似上面 __importDefault 一样层层翻译分析过程略过)
babel 默认的转译规则和 TS 开启 esModuleInterop 的情况差不多,也是通过两个 helper 函数来处理的
// before
import config from 'config';
console.log(config);
// after
"use strict";
var _config = _interopRequireDefault(require("config"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
console.log(_config["default"]);
// before
import * as config from 'config';
console.log(config);
// after
"use strict";
function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
var config = _interopRequireWildcard(require("config"));
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
console.log(config);
_interopRequireDefault 类似 __importDefault
_interopRequireWildcard 类似 __importStar
一般开发中,babel 和 TS 都会配合 webpack 来使用。一般是以下两种方式:
如果是使用 ts-loader,那么 webpack 会将源代码先交给 tsc 来编译,然后处理编译后的代码。经过 tsc 编译后,所有的模块都会变成 cjs,所以 babel 也不会处理,直接交给 webpack 来以 cjs 的方式处理模块。ts-loader实际上就是调用了tsc命令,所以需要tsconfig.json配置文件
如果是使用的 babel-loader,那么 webpack 不会调用 tsc,tsconfig.json 也会被忽略掉。而是直接用 babel 去编译 ts 文件。这个编译过程相比调用 tsc 会轻量许多,因为 babel 只会简单的移除所有 ts 相关的代码,不会做类型检查。一般在这种情况下,一个 ts 模块经过 babel 的 @babel/preset-env 和 @babel/preset-typescript 两个 preset 处理。后者做的事情很简单,仅仅去掉所有 ts 相关的代码,不会处理模块,而前者会将 esm 转成 cjs。babel7开始支持编译ts,这样一来,tsc的存在就被弱化了。 webpack 的 babel-loader实际上就是调用了babel命令,需要babel.config.js配置文件
然而 webpack 的 babel-loader 在调用 babel.transform 时,传了这样一个 caller 选项:
从而导致 babel 保留了 esm 的 import export
tsc、babel可以将esm编译成cjs,但是cjs只有在node环境下才能运行,而 webpack 自己拥有一套模块机制,用来处理 cjs esm AMD UMD 等各种各样的模块,并且为模块提供runtime。因此,需要在浏览器运行的代码最终还需要webpack进行模块化处理
对于 cjs 引用 esm,webpack 的编译机制比较特别:
// 代码经过简化
// before
import cjs from "./cjs";
console.log(cjs);
// after
var cjs = __webpack_require__("./src/cjs.js");
var cjsdefault = __webpack_require__.n(cjs);
console.log(cjsdefault.a);
// before
import esm from "./esm";
console.log(esm);
// after
var esm = __webpack_require__("./src/esm.js");
console.log(esm["default"]);
其中__webpack_require__ 类似于 require,返回目标模块的 module.exports 对象。__webpack_require__.n 这个函数接收一个参数对象,返回一个对象,该返回对象的 a 属性(我也不知道为什么属性名叫 a)会被设为参数对象。所以上面源代码的 console.log(cjs) 会打印出 cjs.js 的 module.exports
由于 webpack 为模块提供了一个 runtime,所以 webpack 处理模块对于 webpack 自己而言很自由,在模块闭包里注入代表 module require exports 的变量就可以了
目前很多常用的包是基于 cjs / UMD 开发的,而写前端代码一般是写 esm,所以常见的场景是 esm 导入 cjs 的库。但是由于 esm 和 cjs 存在概念上的差异,最大的差异点在于 esm 有 default 的概念而 cjs 没有,所以在 default 上会出问题。
TS babel webpack 都有自己的一套处理机制来处理这个兼容问题,核心思想基本都是通过 default 属性的增添和读取
来自:https://segmentfault.com/a/1190000041384332
ES6之前已经出现了js模块加载的方案,最主要的是CommonJS和AMD规范。commonjs主要应用于服务器,实现同步加载,如nodejs。AMD规范应用于浏览器,如requirejs,为异步加载。
Node的https模块:HTTPS服务器使用HTTPS协议,需要证书授权,SSL安全加密后传输,使用443端口
最新版的 node 支持最新版 ECMAScript 几乎所有特性,但有一个特性却一直到现在都还没有支持,那就是从 ES2015 开始定义的模块化机制。而现在我们很多项目都是用 es6 的模块化规范来写代码的,包括 node 项目
module每个文件就是一个模块。文件内定义的变量、函数等等都是在自己的作用域内,都是自身所私有的,对其它文件不可见。在module中有一个属性exports,即:module.exports。它是该模块对外的输出值,是一个对象。
模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的。换言之,一个 Node.js 文件就是一个模块,这个文件可能是JavaScript 代码、JSON 或者编译过的C/C++ 扩展。Node.js 提供了 exports 和 require 两个对象
ES6中引入了模块(Modules)的概念,相信大家都已经挺熟悉的了,在日常的工作中应该也都有使用。本文会简单介绍一下ES模块的优点、基本用法以及常见问题。
ES6中 export 和 export default 与 import使用的区别,使用 react native 代码详解,现在流行的前端框架,angular+ 主要使用 export 导出模块,react native 中使用 export default 导出模块,如今编辑器非常强大,安装插件会自动弹出模块名称,知道其导出怎么使用就可以了
export与export default均可用于导出常量、函数、文件、模块;你可以在其它文件或模块中通过import+(常量 | 函数 | 文件 | 模块)名的方式,将其导入,以便能够对其进行使用;
网上有很多关于export和export default的文章,他们大部门都是只讲了用法,但是没有提到性能,打包等关键的东西。大家应该应该能理解import * from xxx会把文件中export default的内容都打包到文件中,而import {func} from xxx只会把文件中的func导入
模块化主要是用来抽离公共代码,隔离作用域,避免变量冲突等。将一个复杂的系统分解为多个模块以方便编码。会讲述以下内容:CommonJS、AMD 及 核心原理实现、CMD 及 核心原理实现
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!