关于babel:elementui部分引入失效问题追踪

背景我的项目应用vue2与element-ui;最近发现我的项目的局部引入生效了 // babel.config.jsmodule.exports = api => { return { "presets": ['@vue/cli-plugin-babel/preset'], "plugins": [ [ "component", { "libraryName": "element-ui", "styleLibraryName": "theme-chalk" } ] ] };};babel-plugin-component 负责将element-ui的引入代码转换为局部引入代码; 全量的element-ui是被谁引入的这里咱们应用Vue.component这个函数作为切入点;发现Vue.component对于同一个组件(如ElTooltip)调用了2次;看到全量引入的element-ui来自于node_modules中的包;查看了下logic-tree这个包的代码 // 打包前源码import { Button } from 'element-ui';// 打包后var _ks_kwai_ui__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! element-ui */ \"../../node_modules/element-ui/lib/element-ui.common.js\");能够看出包自身代码没有问题,是咱们在打包时babel-plugin-component没能在包文件上失效,导致import语句被webpack翻译为应用了main下的index.js 为什么babel-plugin-component没能失效?能够看出babel-loader没有去编译node_modules下的文件。很好了解,如果所有node_modules代码都要编译,会大大增加打包与热更新工夫;在babel-plugin-component源码中退出log,发现的确没有调用 ImportDeclaration: function ImportDeclaration(path, _ref2) { var opts = _ref2.opts; var node = path.node; var value = node.source.value; var result = {}; if (Array.isArray(opts)) { result = opts.find(function (option) { return option.libraryName === value; }) || {}; } console.log('hy', value); ...通过文档浏览后,发现参数transpileDependencies能够管制node_modules哪些包须要通过babel ...

June 21, 2023 · 1 min · jiezi

关于babel:Babel-基础知识

总体流程源代码 -> AST 树 -> generate 成最终目标代码,如果应用 webapck, babel-loader 会读取 .babel.config.js 或者 .babelrc.js, 这两个文件名的区别是,.babel.config.js个别用于放到根目录下,如果它放到某个文件夹下,解析包依赖的时候会从根目录 node_modules 解析;.babelrc.js 能够放到独自文件夹,也能够放到根目录下,放到某个文件夹下,了解为是对某个文件夹的配置,所以寻找依赖的时候会先从以后文件夹 node_modules 找,而后再往外层找。(在西瓜创作平台应用 yarn workspace 治理仓库时遇到 core-js 版本 2 和 版本 3的依赖解析问题,就是因为子文件夹用的 babel 配置名字是 .babel.config.js, 而不是 .babelrc.js) @babel/core 外围依赖, core 是 babel 编译代码外围逻辑,能够通过 https://astexplorer.net/ 查看语法解析成 ast 的样子 "@babel/code-frame": "^7.8.3", // 用户生成指向源代码未知的错误信息"@babel/generator": "^7.9.0", // 依据最终的 AST 树生成指标代码"@babel/helper-module-transforms": "^7.9.0","@babel/helpers": "^7.9.0", // babel 转换代码的工具函数汇合"@babel/parser": "^7.9.0", // 之前的 Babylon,依据源代码生成初始的 AST 树"@babel/template": "^7.8.6", // 工具类,为 parser 提供模板引擎,更加疾速的转化成 AST"@babel/traverse": "^7.9.0", // 联合 parser, 遍历和更新 AST 树的节点"@babel/types": "^7.9.0", // 用于判断 AST 树各节点类型,对树节点增、删、改、查,从而间接影响源代码@ babel / runtime蕴含 babel 模块运行时工具函数和 regenerator-runtime,也是 @babel/plugin-transform-runtime依赖,敢于提取公共的 helper 函数,结构沙箱垫片和代码复用环境, 防止帮忙函数反复 inject 过多的问题, 该形式的长处是不会净化全局, 适宜在类库开发中应用,和 @babel/preset-env 两种形式取其一即可, 同时应用没有意义, 还可能造成反复的 polyfill 文件(当和useBuiltIns: entry 一起用时)@babel/preset-env(会净化全局, 举荐在业务我的项目中应用)对 es6 以及 es6+ 的语法解析以及应用 core-js 依据 browserslist 给新 api 加 polyfill ...

December 19, 2022 · 1 min · jiezi

关于babel:前端食堂技术周刊第-52-期Babel-7190Fresh-11React-Native-070

美味值: 口味:西瓜挖球冰 食堂技术周刊仓库地址:https://github.com/Geekhyt/weekly本期摘要Babel 7.19.0Fresh 1.1React Native 0.70Node.js 工作原理解析JSON Crack新的 Web 性能指标 INPReact 为什么从新渲染JavaScript 历史的时间轴大家好,我是童欧巴。欢送来到本期的前端食堂技术周刊,咱们先来看下上周的技术资讯。 技术资讯1.Babel 7.19.0该版本包含对如下内容的实现: decorators proposal 5 年了,终于要定下来了,不过性能砍了很多;Duplicate named capturing groups 正则反对反复命名捕捉。2.Fresh 1.1Fresh 1.1 近期公布,更新了好多个性: 默认反对 JSX;反对插件,提供官网 twind 插件;反对 Preact Signals;反对 Preact DevTools;ctx.renderNotFound() 渲染 404 页面;反对多个中间件;实验性反对 Deno.serve;Showcase 展示区和 Made with Fresh 徽章。3.React Native 0.70React Native 0.70 次要更新如下: 文档更新“新的架构“局部;应用 Hermes 作为默认引擎;对立配置 iOS 和 Android 的 Codegen Config;在新架构下 Android 反对库的 Auto-linking ;Android 构建时反对 CMake;等等。上面咱们来看技术材料。 技术材料1.Node.js 工作原理解析Axel Rauschmayer 博士的这篇博文概述了 Node.js 的工作原理,蕴含如下几个方面: ...

September 14, 2022 · 1 min · jiezi

关于babel:构建模块打包器

本文已整顿到 Github,地址  blog。 如果我的内容帮忙到了您,欢送点个 Star  激励激励 :) ~~ 我心愿我的内容能够帮忙你。当初我专一于前端畛域,但我也将分享我在无限的工夫内看到和感触到的货色。 本文的模块打包器来自示例 Minipack,咱们未来理解它是如何一步步实现的。 首先,咱们先来理解实现一个模块打包器所须要依赖的 babel 插件: @babel/traverse — 保护整个树的状态,负责替换、删除和增加节点。@babel/core — Babel 编译器外围。@babel/parser — Babel 中应用的 JavaScript 解析器。@babel/preset-env — 每个环境的 Babel 预设。可依据指标浏览器或运行时环境主动确定所需的 Babel 插件和 polyfills,从而将 ES6+ 编译至 ES5。咱们替换了该示例的旧的,曾经被并入 babel 内的插件。构建一个简略的模块打包器只须要三个步骤: 利用 babel 实现代码转换,并生成单个文件的依赖生成依赖图谱生成最初打包代码转换代码、生成依赖首先,咱们创立一个 createAsset() 函数,该函数将承受 filename 参数(文件门路),读取内容并提取它的依赖关系。 const fs = require('fs')function createAsset(filename) { const content = fs.readFileSync(filename, 'utf-8')}咱们应用 fs.readFileSync 读取文件,并返回文件内容。 依据其内容,咱们能够获取到 import 字符串(依赖的文件)。 这里咱们用到 JavaScript 解析器 — @babel/parser,它是读取和了解 JavaScript 代码的工具。它生成一个更形象的模型,称为 AST(形象语法树)。 AST 蕴含很多对于咱们代码的信息。咱们能够查问它理解咱们的代码正在尝试做什么。 ...

June 24, 2022 · 3 min · jiezi

关于babel:babel插件手册md语法修复版本

Babel的官网插件手册的中文翻译版存在md语法错误,本文是md语法整顿后的文档。当然,更加倡议你浏览英文原版,本文能够做一个疾速查阅文档。这篇文档涵盖了如何创立 Babel 插件等方面的内容。. 这本手册提供了多种语言的版本,查看 自述文件 里的残缺列表。 目录介绍根底 形象语法树(ASTs)Babel 的解决步骤解析 词法剖析语法分析转换生成遍历Visitors(访问者)Paths(门路) Paths in Visitors(存在于访问者中的门路)State(状态)Scopes(作用域) Bindings(绑定)API babylonbabel-traversebabel-typesDefinitions(定义)Builders(构建器)Validators(验证器)Converters(变换器)babel-generatorbabel-template编写你的第一个 Babel 插件转换操作 拜访获取子节点的Path查看节点(Node)类型查看门路(Path)类型查看标识符(Identifier)是否被援用找到特定的父门路获取同级门路进行遍历解决替换一个节点用多节点替换单节点用字符串源码替换节点插入兄弟节点插入到容器(container)中删除节点替换父节点删除父节点Scope(作用域)查看本地变量是否被绑定生成UID晋升变量申明至父级作用域重命名绑定及其援用插件选项 插件的筹备和收尾工作在插件中启用其余语法构建节点最佳实际 尽量避免遍历形象语法树(AST)及时合并访问者对象能够手动查找就不要遍历优化嵌套的访问者对象注意嵌套构造单元测试 介绍Babel 是一个通用的多功能的 JavaScript 编译器。此外它还领有泛滥模块可用于不同模式的动态剖析。 动态剖析是在不须要执行代码的前提下对代码进行剖析的处理过程 (执行代码的同时进行代码剖析即是动态分析)。 动态剖析的目标是多种多样的, 它可用于语法查看,编译,代码高亮,代码转换,优化,压缩等等场景。你能够应用 Babel 创立多种类型的工具来帮忙你更有效率并且写出更好的程序。 在 Twitter 上关注 @thejameskyle,第一工夫获取更新。 根底Babel 是 JavaScript 编译器,更确切地说是源码到源码的编译器,通常也叫做“转换编译器(transpiler)”。 意思是说你为 Babel 提供一些 JavaScript 代码,Babel 更改这些代码,而后返回给你新生成的代码。 形象语法树(ASTs)这个处理过程中的每一步都波及到创立或是操作形象语法树,亦称 AST。 Babel 应用一个基于 ESTree 并批改过的 AST,它的内核阐明文档能够在这里. com/babel/babel/blob/master/doc/ast/spec. md)找到。.function square(n) { return n * n;}AST Explorer 能够让你对 AST 节点有一个更好的感性认识。 这里是上述代码的一个示例链接。这个程序能够被示意成如下的一棵树: - FunctionDeclaration: - id: - Identifier: - name: square - params [1] - Identifier - name: n - body: - BlockStatement - body [1] - ReturnStatement - argument - BinaryExpression - operator: * - left - Identifier - name: n - right - Identifier - name: n或是如下所示的 JavaScript Object(对象): ...

March 28, 2022 · 16 min · jiezi

关于babel:使用elementui时按需引入配置babelrc文件时输入es2015报错的问题

把.babelrc文件中的"es2015"批改为"@babel/preset-env",而后从新再跑一下我的项目即可,整体文件如下所示: { "presets": [ [ "@babel/preset-env", { "modules": false } ] ], "plugins": [ [ "component", { "libraryName": "element-ui", "styleLibraryName": "theme-chalk" } ] ]}这是我在开发过程中遇到的问题(官网文档给的是“es2015”),特此记录一下,避免再犯!

February 25, 2022 · 1 min · jiezi

关于babel:Babel

一、装置npm install bable-preset-env babel-cli --save-dev二、创立文件并配置文件:.babelrc{ "preset": ["env"] }三、命令【ES6 -> ES5】// 单个文件babel src/index.js -o dist/index.js// 文件夹babel src -d dist// 实时监控babel src -w -d dist

January 22, 2022 · 1 min · jiezi

关于babel:js按需导入模块

为了升高首屏代码大小,对于一些大的第三方库或者团队的根底工具库,须要按需导入模块。如: import Button from 'antd/lib/button';但这在须要导入十分多的组件场景时,开发繁琐,体验不敌对。在这些组件库的官网文档或者社区会举荐一些babel插件,帮忙达到良好的开发体验和性能优化。 本文将具体探索这些工具的原理。 antd等UI组件库按需加载在应用antd的老版本时,会举荐应用babel-plugin-import工具按需导入组件。工具能够做到如下的转换: import { Button } from 'antd';ReactDOM.render(<Button>xxxx</Button>); ↓ ↓ ↓ ↓ ↓ ↓var _button = require('antd/lib/button');require('antd/lib/button/style');ReactDOM.render(<_button>xxxx</_button>);antd和element-ui都叫做按需加载,但我感觉叫做按需导入更加贴合意境。eleemnt-ui配套按需导入工具为babel-plugin-component。babel-plugin-import工具,会在编译时会剖析模块导入语句。当为须要导入的指标库组件时,会将原导入删除,生成多个新的导入(如果你配置了style,会额定导入款式)。因为从新生成的导入语句会导致模块变量发生变化(如上文案例演示中Button会被转换为_button),这导致插件程序还会剖析以后模块的所有变量,对应用原变量的语句中,将变量名修复。 最新的antd曾经举荐应用webpack的tree shaking机制来按需加载。babel-plugin-import也根本没什么更新。对于vue生态来说,很多组件库为了反对全局注册的形式,无奈应用tree shaking。尽管babel-plugin-import等工具反对一些配置定制,但还是存在上面毛病: 每个插件都是针对特定的组件库,须要合乎特定的目录和文件维护标准。如babel-plugin-import导入的模块须要反对目录为: |--component|----index.js|----*.js|----style|------index.js|------*.cssbabel-plugin-import因为底层实现扭转了导入的模块变量,而后再全模块枚举语句类型中找到应用变量将其修复,再某一些十分不常见的语句中,会呈现没有转变模块变量导致语法错误问题。 对任意库反对按需导入如果也想对我的项目中公共根底模块(公共组件,公共工具文件等)反对源码中全量导入但理论按需加载的成果,除了能够fork babel-plugin-import等工具调整逻辑来实现,还能够应用工具babel-plugin-transform-imports来反对。 babel-plugin-transform-imports是本人配置转换格局,如能够达到上面成果: import { Row, Grid as MyGrid } from 'react-bootstrap';import { merge } from 'lodash'; ↓ ↓ ↓ ↓ ↓ ↓ import Row from 'react-bootstrap/lib/Row';import MyGrid from 'react-bootstrap/lib/Grid';import merge from 'lodash/merge';此时须要的配置为: { "plugins": [ ["transform-imports", { "react-bootstrap": { "transform": "react-bootstrap/lib/${member}", "preventFullImport": true }, }] ]}transform是能够反对函数的,实现高级定制,扩展性十分高。且babel-plugin-transform-imports实现的形式是间接对导入语句进行了修复,比babel-plugin-import更加优雅和适用性更广,浏览他的源码也能够发现比babel-plugin-import简洁的多。 ...

November 18, 2021 · 1 min · jiezi

关于babel:保姆级教学这次一定学会开发babel插件

如果你有babel相干常识根底倡议间接跳过 前置常识 局部,间接返回 "插件编写" 局部。 前置常识什么是AST学习babel, 必备常识就是了解AST。 那什么是AST呢? 先来看下维基百科的解释: 在计算机科学中,形象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种形象示意。它以树状的模式体现编程语言的语法结构,树上的每个节点都示意源代码中的一种构造"源代码语法结构的一种形象示意" 这几个字要划重点,是咱们了解AST的要害,说人话就是依照某种约定好的标准,以树形的数据结构把咱们的代码形容进去,让js引擎和转译器可能了解。 举个例子: 就好比当初框架会利用`虚构dom`这种形式把`实在dom`构造形容进去再进行操作一样,而对于更底层的代码来说,AST就是用来形容代码的好工具。当然AST不是JS特有的,每个语言的代码都能转换成对应的AST, 并且AST构造的标准也有很多, js里所应用的标准大部分是 estree ,当然这个只做简略理解即可。 AST到底长啥样理解了AST的基本概念, 那AST到底长啥样呢? astexplorer.net这个网站能够在线生成AST, 咱们能够在外面进行尝试生成AST,用来学习一下构造 babel的处理过程问:把冰箱塞进大象有几个阶段? 关上冰箱 -> 塞进大象 -> 关上冰箱 babel也是如此,babel利用AST的形式对代码进行编译,首先天然是须要将代码变为AST,再对AST进行解决,解决完当前呢再将AST 转换回来 也就是如下的流程 code转换为AST -> 解决AST -> AST转换为code而后咱们再给它们一个业余一点的名字 解析 -> 转换 -> 生成解析(parse)通过 parser 把源码转成形象语法树(AST)这个阶段的次要工作就是将code转为AST, 其中会通过两个阶段,别离是词法剖析和语法分析。当parse阶段开始时,首先会进行文档扫描,并在此期间进行词法剖析。那怎么了解此法剖析呢如果把咱们所写的一段code比喻成句子,词法剖析所做的事件就是在拆分这个句子。如同 “我正在吃饭” 这句话,能够被拆解为“我”、“正在”、“吃饭”一样, code也是如此。比方: const a = '1'会被拆解为一个个最细粒度的单词(tokon): 'const', 'a', '=', '1'这就是词法分析阶段所做的事件。 词法剖析完结后,将剖析所失去的 tokens 交给语法分析, 语法分析阶段的工作就是依据 tokens 生成 AST。它会对 tokens 进行遍历,最终依照特定的构造生成一个 tree 这个 tree 就是 AST。 ...

September 27, 2021 · 6 min · jiezi

关于babel:Babel-的原理

残缺高频题库仓库地址:https://github.com/hzfe/aweso... 残缺高频题库浏览地址:https://febook.hzfe.org/ 相干问题Babel 是什么Babel 有什么用压缩代码如何实现答复关键点JS 编译器 AST 插件零碎 Babel 是 JavaScript 编译器:他能让开发者在开发过程中,间接应用各类方言(如 TS、Flow、JSX)或新的语法个性,而不须要思考运行环境,因为 Babel 能够做到按需转换为低版本反对的代码;Babel 外部原理是将 JS 代码转换为 AST,对 AST 利用各种插件进行解决,最终输入编译后的 JS 代码。 知识点深刻1. AST 形象语法树简略定义:以树的模式来体现编程语言的语法结构。 利用在线 playground 调试,能够对 AST 有个直观感触:生成的树有多个节点,节点有不同的类型,不同类型节点有不同的属性。 const custom = "HZFE"; AST 是源代码的高效示意,能便捷的示意大多数编程语言的构造。实用于做代码剖析或转换等需要。之所以用树来进行剖析或转换,是因为树能使得程序中的每一节点恰好被拜访一次(前序或后续遍历)。 常见应用场景:代码压缩混同性能能够借助 AST 来实现:剖析 AST,基于各种规定进行优化(如 IF 语句优化;移除不可拜访代码;移除 debugger 等),从而生成更小的 AST 树,最终输入精简的代码后果。 2. Babel 编译流程三大步骤 解析阶段:Babel 默认应用 @babel/parser 将代码转换为 AST。解析个别分为两个阶段:词法剖析和语法分析。词法剖析:对输出的字符序列做标记化(tokenization)操作。语法分析:解决标记与标记之间的关系,最终造成一颗残缺的 AST 构造。转换阶段:Babel 应用 @babel/traverse 提供的办法对 AST 进行深度优先遍历,调用插件对关注节点的处理函数,按需对 AST 节点进行增删改操作。生成阶段:Babel 默认应用 @babel/generator 将上一阶段解决后的 AST 转换为代码字符串。3. Babel 插件零碎Babel 的外围模块 @babel/core,@babel/parser,@babel/traverse 和 @babel/generator 提供了残缺的编译流程。而具体的转换逻辑须要插件来实现。 ...

September 20, 2021 · 1 min · jiezi

关于babel:一口气了解babel

最近几年,如果你是一名前端开发者,如果你没有应用甚至据说过 babel,可能会被当做穿梭者吧? 说到 babel,一连串名词会蹦出来: babel-clibabel-corebabel-runtimebabel-nodebabel-polyfill...这些都是 babel 吗?他们别离是做什么的?有区别吗? babel 到底做了什么?怎么做的?简略来说把 JavaScript 中 es2015/2016/2017/2046 的新语法转化为 es5,让低端运行环境(如浏览器和 node )可能意识并执行。本文以 babel 6.x 为基准进行探讨。最近 babel 出了 7.x,放在最初聊。 严格来说,babel 也能够转化为更低的标准。但以目前状况来说,es5 标准曾经足以笼罩绝大部分浏览器,因而惯例来说转到 es5 是一个平安且风行的做法。 如果你对 es5/es2015 等等也不理解的话,那你可能真的须要先补补课了。 应用办法总共存在三种形式: 应用单体文件 (standalone script)命令行 (cli)构建工具的插件 (webpack 的 babel-loader, rollup 的 rollup-plugin-babel)。其中前面两种比拟常见。第二种多见于 package.json 中的 scripts 段落中的某条命令;第三种就间接集成到构建工具中。 这三种形式只有入口不同而已,调用的 babel 内核,解决形式都是一样的,所以咱们先不纠结入口的问题。 运行形式和插件babel 总共分为三个阶段:解析,转换,生成。 babel 自身不具备任何转化性能,它把转化的性能都合成到一个个 plugin 外面。因而当咱们不配置任何插件时,通过 babel 的代码和输出是雷同的。 插件总共分为两种: 当咱们增加 语法插件 之后,在解析这一步就使得 babel 可能解析更多的语法。(顺带一提,babel 外部应用的解析类库叫做 babylon,并非 babel 自行开发)举个简略的例子,当咱们定义或者调用办法时,最初一个参数之后是不容许减少逗号的,如 callFoo(param1, param2,) 就是非法的。如果源码是这种写法,通过 babel 之后就会提醒语法错误。 ...

August 18, 2021 · 5 min · jiezi

关于babel:Babel-register

本节咱们来讲一下 @babel/register。应用 Babel 的办法之一就是通过 require 钩子(hook),require 钩子将本身绑定到 node 的 require 模块上,并在运行时主动编译文件。这和 CoffeeScript 的 coffee-script/register 相似。 装置@babel/register 只有一个性能,就是重写 node 的 require 办法。 装置命令如下所示: npm install @babel/core @babel/register --save-dev装置好后能够通过 require 援用,如下所示: require("@babel/register");@babel/register 在底层改写了 node 的 require 办法,在代码里引入 @babel/register 模块后,所有通过require引入并且以 .es6,.es,.jsx, .mjs,和 .js 为后缀名的模块都会被 Babel 转译。 默认疏忽node_modules默认状况下,所有对 node_modules 目录下的文件的 require 申请都将被疏忽。咱们能够通过以下形式传递一个用于匹配被疏忽文件的正则表达式来批改默认行为: require("@babel/register")({ ignore: [],});指定参数require("@babel/register")({ ignore: [ /regex/, function(filepath) { return filepath !== "/path/to/es6-file.js"; }, ], only: [ /my_es6_folder/, function(filepath) { return filepath === "/path/to/es6-file.js"; } ], extensions: [".es6", ".es", ".jsx", ".js", ".mjs"], cache: true,});或者还能够传递其余的参数,例如 plugins 和 presets。须要留神的是,配置文件也将被加载,并且编程形式的配置也将被合并进来,放在这些配置项的顶部。 ...

August 10, 2021 · 1 min · jiezi

关于babel:Babel-transformruntime

Babel @babel/plugin-transform-runtime本节咱们来学习 @babel/plugin-transform-runtime 和 @babel/runtime 。 Babel 中应用辅助函数来实现常见性能,例如 _extend() 函数,每个编译后的文件都须要定义它所须要应用的辅助函数。但这样显然会造成很多反复,所以 Babel 把所有的辅助函数都封装于 @babel/runtime,每个编译后的文件只须要援用 @babel/runtime 即可。 @babel/runtime 插件能够将工具函数的代码转换成 require 语句,指向为对 @babel/runtime 的援用。每当要转译一个 API 时,都要手动加上 require('@babel/runtime')。简略说 @babel/runtime 更像是一种按需加载的实现,比方哪里须要应用 Promise,只有在这个文件头部增加如下代码即可: require Promise from '@babel/runtime/core-js/promise'而为了方便使用 @babel/runtime 插件,解决手动 require 的苦恼。它会剖析咱们的 ast 中,是否有援用 @babel/runtime (通过映射关系),如果有就会在以后模块顶部插入咱们须要的垫片。 transform-runtime 是利用插件自动识别并替换代码中的新个性,所以不须要再引入,只须要装好 @babel/runtime 和配置 plugin 就能够了。 装置配置大多数状况下,咱们应该装置 @babel/plugin-transform-runtime 作为开发依赖项,即在装置命令中加上 --save-dev,并且将 @babel/runtime 作为生产依赖项,在装置命令中应用 --save。 装置命令如下所示: > npm install --save-dev @babel/plugin-trabsform-runtime> npm install --save @babel/runtime装置好后,咱们能够在 .babelrc 配置文件中进行配置,@babel/plugin-transform-runtime 是否要开启某项性能,都是在配置项里设置的,某些配置项的设置是须要装置 npm 包。@babel/plugin-transform-runtime 在没有设置配置的时候,其配置项参数取默认值。 上面两个配置成果是一样的: // 默认配置{ "plugins": [ "@babel/plugin-transform-runtime" ]}// 设置其配置项{ "plugins": [ [ "@babel/plugin-transform-runtime", { "helpers": true, "corejs": false, "regenerator": true, "useESModules": false, "absoluteRuntime": false, "version": "7.0.0-beta.0" } ] ]}配置项解说helpers:该配置项用来设置是否要主动引入辅助函数包,取值为布尔值,默认为 true。切换是否用对moduleName的调用替换内联的babel帮忙程序(类调用查看、扩大等)。 ...

August 8, 2021 · 1 min · jiezi

关于babel:Babel-babelpolyfil

Babel 中默认只转换新的 JavaScript 句法,例如箭头函数、扩大运算符等。然而不会转换新的 API,像是Set、Maps、Iterator、Generator 、Symbol、Reflect 等全局对象,以及一些定义在全局对象上的办法都不会进行转译。如果想应用这些新的对象和办法,则须要为以后环境提供一个 polyfill 垫片。 举一个例子,例如 ES6 在 Array 对象上有一个新增的 Array.from 办法,因为这个办法是全局对象上的办法,所以 Babel 就不会对这个办法进行转译。如果想让这个办法运行,就要应用 @babel/polyfill 为以后环境提供一个垫片。 装置@babel/polyfil目前最罕用的配合 Babel 一起应用的 polyfill 垫片就是 @babel/polyfil,通过改写全局 prototype 的形式实现,它会加载整个 polyfil,针对编译的代码中新的 API 进行解决,并且在代码中插入一些帮忙函数,比拟适宜独自运行的我的项目。 装置命令如下所示: $ npm install --save @babel/polyfil装置好后咱们能够在程序入口文件的顶部援用 @babel/polyfil:: require('@babel/polyfill')[].findIndex('babel')或者应用 ES6 的语法,应用 import 导入: import '@babel/polyfill'[].findIndex('babel')babel-polyfill 解决了 Babel 不转换新 API 的问题,然而间接在代码中插入帮忙函数,会导致净化了全局环境,并且不同的代码文件中蕴含反复的代码,导致编译后的代码体积变大。尽管这对于应用程序或命令行工具来说可能是坏事,但如果已有代码打算公布为能够供其他人应用的库,或咱们无奈齐全管制代码运行的环境,则会成为问题。 留神须要留神的是从 Babel 7.4.0 开始,不再举荐应用 @babel/polyfill 包,而是间接应用 core-js/stable 和 regenerator-runtime/runtime,如下所示: import "core-js/stable";import "regenerator-runtime/runtime";链接:https://www.9xkd.com/

August 6, 2021 · 1 min · jiezi

关于babel:Babel-预设

本节咱们来学习 Babel 中的预设 Presets。如果咱们不想手动的组合插件,能够应用 preset 作为 Babel 插件的组合,或者共享 options 配置。 Babel7.8 官网的插件和预设有一百多种,如果咱们一个个学习那须要大量的工夫。然而咱们也没必要全副学习,因为在理论应用中,罕用的预设和插件也只有那几个,咱们重点把握这些罕用的即可,把握后这样其余的插件和预设就变得简略啦。 官网Preset官网针对罕用的环境编写了一些 preset,如下所示: @babel/preset-env@babel/preset-flow@babel/preset-react@babel/preset-typescript这几个 preset 是社区保护的,能够通过 npm 命令下载。咱们能够依据我的项目须要来下载应用,例如一个一般的 vue 我的项目,Babel 的官网预设中,只须要配一个 @babel/preset-env 就能够啦。 Stage-X试验预设stage-x 预置中的语法转换会随着被批准为 JavaScript 新版本的组成部分而进行相应的扭转(例如 ES6/ES2015)。 TC39 将提案分为以下几个阶段: Stage 0 – 构想(Strawman):只是一个想法,可能有 Babel 插件。Stage 1 – 倡议(Proposal):这是值得跟进的。Stage 2 – 草案(Draft):初始标准。Stage 3 – 候选(Candidate):实现标准并在浏览器上初步实现。Stage 4 – 实现(Finished):将增加到下一个年度版本公布中。创立preset如果咱们须要创立一个本人的 preset,只需导出一份配置即可。 示例: 能够是一份插件数组,例如: module.exports = function() { return { plugins: [ "pluginA", "pluginB", "pluginC", ] };}preset 能够蕴含其余的 preset,以及带有参数的插件: module.exports = () => ({ presets: [ require("@babel/preset-env"), ], plugins: [ [require("@babel/plugin-proposal-class-properties"), { loose: true }], require("@babel/plugin-proposal-object-rest-spread"), ],});预设的门路如果 preset 在 npm 上,咱们能够输出 preset 的名称,babel 将查看是否曾经将其装置到 node_modules 目录下: ...

August 4, 2021 · 1 min · jiezi

关于babel:Babel-插件

Babel 是一个编译器,和其余编译器一样,编译过程分为三个阶段,别离是解析(parsing)、转换(transforming)和生成(generate)。其中剖析和生成阶段由 Babel 外围实现,而转换阶段,则由Babel插件实现。所以如果咱们要实现代码的转换,须要为 Babel 增加插件。Babel 也提供了很多的接口来供咱们编写本身的插件来转换咱们的理论代码。 插件的介绍上面是一个典型的 Babel 插件构造: export default function({ types: babelTypes }) { return { visitor: { Identifier(path, state) {}, ASTNodeTypeHere(path, state) {} } };};其中咱们须要关注的内容如下: babelType:相似 lodash 那样的工具集,次要用来操作 AST 节点,比方创立、校验、转变等。例如判断某个节点是不是标识符。path:AST 中有很多节点,每个节点可能有不同的属性,并且节点之间可能存在关联。path 是个对象,它代表了两个节点之间的关联。咱们能够在 path 上拜访到节点的属性,也能够通过 path 来拜访到关联的节点。state:代表了插件的状态,咱们能够通过 state 来拜访插件的配置项。visitor:Babel 采取递归的形式拜访 AST 的每个节点,之所以叫做 visitor,只是因为有个相似的设计模式叫做访问者模式,不必在意背地的细节。Identifier、ASTNodeTypeHere:AST 的每个节点,都有对应的节点类型,比方标识符、函数申明等,能够在 visitor 上申明同名的属性,当 Babel 遍历到相应类型的节点,属性对应的办法就会被调用,传入的参数就是 path、state。插件的应用例如咱们来实现一个简略的插件,将所有名称为 hello 的标识符,转成 xkd。 首先要确保曾经装置了 @babel/cli 依赖,如果没有能够执行下述命令: npm install --save-dev @babel/cli而后能够开始创立插件,判断标识符的名称是否是 a,如果是则替换成 xkd,plugin.js 文件内容如下所示: module.exports = function({ types: babelTypes }) { return { name: "simple-plugin-replace", visitor: { Identifier(path, state) { if (path.node.name === 'a') { path.node.name = 'xkd'; } } } };};而后在 index.js 文件中编写源代码,例如: ...

August 2, 2021 · 1 min · jiezi

关于babel:Babel-配置文件

本节咱们学习 Babel 的配置,许多工具都有本人的配置文件,例如ESLint 的配置文件为 .eslintrc,Prettier 的配置文件为 .prettierrc。 Babel 也有配置文件, Babel 的配置文件是 Babel 执行时默认会在当前目录寻找的文件,次要有 .babelrc、.babelrc.js、babel.config.js、package.json 文件。 .babelrc.babelrc 文件后面咱们应用过,咱们能够在我的项目根目录下创立一个 .babelrc 文件,而后输出如下内容: { "presets": [...], "plugins": [...] }.babelrc.js.babelrc.js 文件的配置和 .babelrc 的雷同,只是文件格式不同,一个是 json 文件,一个是 js 文件。 const presets = [ ... ];const plugins = [ ... ];module.exports = { presets, plugins };还能够调用Node.js 的任何 API,例如基于过程环境进行动静配置: const presets = [ ... ];const plugins = [ ... ];if (process.env["ENV"] === "prod") { plugins.push(...);}module.exports = { presets, plugins };babel.config.js在我的项目根目录下创立一个 babel.config.js 文件,能够通过 module.exports 输入配置项,例如: ...

July 31, 2021 · 1 min · jiezi

关于babel:Babel-编辑器

咱们在学习 Babel 的应用能够抉择一个编辑器。当初很多风行的编辑器都反对 ES2015+ 语法高亮显示,并且此性能开箱即用,而后有一些编辑器则须要装置额定的扩大。 咱们能够抉择的编辑器有: Sublime Text 3AtomVimVisual Studio CodeWebStorm在这几个编辑器中,抉择一个你喜爱的下载安装,例如 Visual Studio Code 编辑器。 装置Visual Studio CodeVisual Studio Code 下载地址:https://code.visualstudio.com/。 下载后双击 .exe 安装包,而后依据提醒进行装置。装置实现后,关上 Visual Studio Code 软件: 而后就能够开始出创立我的项目并应用啦。例如上一节咱们创立好的 my_babel 我的项目,如果要在 Visual Studio Code 中关上这个我的项目,咱们能够找到 File 抉择 open Folder... 选项,而后找到 my_babel,例如:

July 28, 2021 · 1 min · jiezi

关于babel:Chrome团队如何曲线拯救KPI

大家好,我是卡颂。 当聊到Chrome,你第一反馈是啥? 市占率第一的浏览器?鼎鼎大名的V8引擎?浏览器调试的标配——DevTools? 对于Chrome团队成员来说,第一反馈很可能是这两个指标(KPI): UX(user experience)用户体验DX(developer experience)开发者体验作为开发者,置信你能感触到诸多围绕这两个指标的改良: 底层V8、webassembly引擎的迭代lighthouse工具对UX、DX指标的定量分析Chrome对ES规范新个性的疾速反对当所有都做到极致后,围绕这两个指标还有什么可开掘的呢(KPI能写啥呢)? 让咱们一起看看Chrome团队为了更好的web体验,做了哪些曲线救国的致力。 逻辑要顺这里的逻辑是这样的: 当今世界大部分web我的项目依赖开源工具更好的开源工具带来更好的web体验依照这个逻辑,只有咱们(Chrome团队)与开源我的项目单干,让他们变得更好,那就是为更好的web体验做奉献(也就能援救KPI了)。 所以,只须要筛选适合的我的项目,依据其适宜的优化类型(UX、DX),开展深度单干就行。 接下来,让咱们看看一些与Chrome团队单干的我的项目。 与Next.js单干Next.js作为基于React的全功能生产可用框架,其SSR性能始终与React团队深度单干。 Chrome团队基于SSR这一场景,为Next.js定制了一系列Timing API。 新Timing API将SSR相干指标纳入统计(比方hydrate工夫)。 同时,LightHouse工具能够收集更多SSR相干数据供参考: 与Babel单干咱们罕用@babel/preset-env依据指标浏览器版本将高级ES语法编译为ES5语法。 这种降级编译的实现思路为:每个高级语法能够看作一或多个语法转换的汇合。 在遇到高级语法时,将其替换为这些语法转换的实现。 举个例子:函数参数能够作为解构、参数默认值、残余参数这3个个性的汇合。对于如下源代码: const foo = ({ a = 1 }, b = 2, ...args) => [a,b,args];通过@babel/preset-env编译后的输入蕴含了解构、参数默认值、残余参数这3个个性的实现: const foo = function foo(_ref, b) { let { a = 1 } = _ref; if (b === void 0) { b = 2; } for ( var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++ ) { args[_key - 2] = arguments[_key]; } return [a, b, args];};能够看到,编译后总体代码量激增! ...

July 26, 2021 · 1 min · jiezi

关于babel:Babel-安装

本节咱们来学习如何装置 Babel。装置 Babel 后咱们就能够将应用 ES2015 + 语法的 JavaScript 利用程序代码,编译为可在以后浏览器中应用的代码。本教程咱们学习 Babel7 版本的常识。 筹备工作Babel 是基于 Node.js 的,如果咱们要装置 Babel ,那么须要先装置好 Node.js。 Node.js 的官网地址:https://nodejs.org/en/。而后依照提醒进行装置即可。 装置好后,咱们能够在命令工具中输出 node -v 和 npm -v 来检测一下 Node.js 和 npm 是否装置胜利,如果呈现版本号则示意装置胜利,如果所示: 创立我的项目首先咱们来创立一个我的项目,在命令工具中应用 cd 命令进去指定门路,在指定门路下执行如下命令: mkdir my_babel命令执行结束后,指定门路中会胜利创立一个名为 my_babel 的文件夹,如下图:而后须要应用 cd 命令进入这个我的项目门路,接着执行初始化命令: npm init在 node 开发中执行上述命令会生成一个 pakeage.json 文件,这个文件次要是用来记录这个我的项目的详细信息的,它会将咱们在我的项目开发中所要用到的包,以及我的项目的详细信息等记录在这个我的项目中。在当前的版本迭代和我的项目移植的时候会更加的不便。并且应用 npm init 初始化我的项目还有一个益处,就是在进行我的项目传递的时候不须要将我的项目依赖包一起发送给对方,对方在承受到你的我的项目之后再执行 npm install 就能够将我的项目依赖全副下载到我的项目里。 如下图所示,执行命令后会让咱们填写一些配置信息,如果还不晓得怎么填写的话能够一路回车:如果想要所有的配置项都抉择默认选项,能够间接执行 npm init -y 命令,这样能疾速生成一个 package.json 文件。 package name :字段示意项目名称。version: 示意版本号。description: 示意对我的项目的形容。entry point:我的项目的入口文件。test command:我的项目启动的时候要用什么命令来执行脚本文件。git repository:如果你要将我的项目上传到 git 中的话,那么就须要填写 git 的仓库地址。keywirds:我的项目关键字。author:作者的名字。license:发行我的项目须要的证书。开始应用咱们在应用 Babel 之前,大抵理解一下 Babel 的各个组成部分,上面是 Babel 中一些罕用的重要局部: ...

July 25, 2021 · 2 min · jiezi

关于babel:Babel-简介

本教程咱们学习 Babel 的基础知识,Babel 是一个用于 web 开发,且自在开源的 JavaScript 编译器、转换器。次要用于在以后和较旧的浏览器或环境中将 ECMAScript 2015+ 代码转换为 JavaScript 的向后兼容版本。 Babel 使软件开发者可能以偏好的编程语言或格调来写作源代码,并将其利用 Babel 翻译成 JavaScript,是现今在浏览器最罕用的编程语言。 下列是 Babel 的应用场景: 语法转换。指标环境中短少的 Polyfill 性能。源代码转换(codemods)示例:例如将 ES2015 中的箭头函数编译成 ES5: [1, 2, 3].map((n) => n + 1);编译后的 ES5 代码如下所示: [1, 2, 3].map(function (n) { return n + 1;});这两段代码的性能是一样的,然而因为 ES2015 和 ES5 的语法有所不同,所以编译后的代码也不同。 Babel运行形式和插件Babel 的编译总共分为三个阶段:解析(parsing),转换(transformation),生成(generate)。 Babel 自身不具备任何转化性能,Babel 的转换性能都是通过插件(plugin)来实现的,把转化的性能都合成到一个个插件外面。因而当咱们不配置任何插件时,通过 Babel 的代码和输出是雷同的。 插件总共分为两种: 语法插件:当咱们增加语法插件之后,在解析这一步就使得 Babel 可能解析更多的语法。转译插件:而增加转译插件之后,在转换这一步把源码转换并输入。这也是咱们应用 Babel 最实质的需要。同一类语法可能同时存在语法插件版本和转译插件版本。如果咱们应用了转译插件,就不必再应用语法插件了。 presetpreset 预约义的一系列插件的组合,用于将特定的语法转换为以后环境应用的语法,防止了本人独自去筛选插件。 preset 分为以下几种: 官网内容,目前包含 env、react、flow、minify 、typescript 等。stage-x,这外面蕴含的都是当年最新标准的草案,每年更新。能够细分为: ...

July 23, 2021 · 1 min · jiezi

关于babel:转剖析-BabelBabel-总览

名词解释 AST:Abstract Syntax Tree, 形象语法树 DI: Dependency Injection, 依赖注入 =============================================================== Babel 的解析引擎Babel 应用的引擎是 babylon,babylon 并非由 babel 团队本人开发的,而是 fork 的 acorn 我的项目,acorn 的我的项目自己在很早之前在趣味部落 1.0 在构建中应用,为了是做一些代码的转换,是很不错的一款引擎,不过 acorn 引擎只提供根本的解析 ast 的能力,遍历还须要配套的 acorn-travesal, 替换节点须要应用 acorn-,而这些开发,在 Babel 的插件体系开发下,变得一体化了 Babel 的工作过程Babel 会将源码转换 AST 之后,通过便当 AST 树,对树做一些批改,而后再将 AST 转成 code,即成源码。 下面提到 Babel 是 fork acon 我的项目,咱们先来看一个来自趣味部落我的项目的,简略的 ACON 示例 一个简略的 ACON 转换示例解决的问题 将 Model.task('getData', function($scope, dbService){ });转换成 Model.task('getData', ['$scope', 'dbService', function($scope, dbService){ }]);相熟 angular 的同学都能看到这段代码做的是对 DI 的主动提取性能,应用 ACON 手动撸代码 ...

July 17, 2021 · 5 min · jiezi

关于babel:转babelparser和acorn的区别

晓得acorn是js的解析器,也晓得babel-parser也是js的解析器,然而他们两个有什么区别吗?首先,@babel/parser(之前就是babylon)是从acorn fork 进去的,只是根本都被重写了,然而有些acorn的算法仍热被沿用下来了。 他们区别还是有一些的: @babel/parser不反对第三方的插件。acorn只反对第四阶段的提案(根本等于写入规范了,只是工夫的问题 见此)。AST的格局不同,不过能够启动@babel/parser的estree插件来和acorn的AST格局匹配babel/issues#11393

July 17, 2021 · 1 min · jiezi

关于babel:转载解剖Babel-向前端架构师迈出一小步

解剖Babel —— 向前端架构师迈出一小步 当聊到Babel的作用,很多人第一反馈是:用来实现API polyfill。 事实上,Babel作为前端工程化的基石,作用远不止这些。 作为一个宏大的家族,Babel生态中有很多概念,比方:preset、plugin、runtime等。 这些概念使初学者对Babel望而却步,对其了解也止步于webpack的babel-loader配置。 本文会从Babel的外围性能登程,一步步揭开Babel大家族的神秘面纱,向前端架构师迈出一小步。 Babel是什么 Babel 是一个 JavaScript 编译器。作为JS编译器,Babel接管输出的JS代码,通过外部解决流程,最终输入批改后的JS代码。 在Babel外部,会执行如下步骤: 1、将Input Code解析为AST(形象语法树),这一步称为parsing2、编辑AST,这一步称为transforming3、将编辑后的AST输入为Output Code,这一步称为printing 从Babel仓库[1]的源代码,能够发现:Babel是一个由几十个我的项目组成的Monorepo。 其中babel-core提供了以上提到的三个步骤的能力。 在babel-core外部,更粗疏的讲: babel-parser实现第一步babel-generator实现第三步要理解第二步,咱们须要简略理解下AST。AST的构造进入AST explorer[2],抉择@babel/parser作为解析器,在左侧输出: const name = ['ka', 'song'];能够解析出如下构造的AST,他是JSON格局的树状构造: 在babel-core外部: babel-traverse能够通过「深度优先」的形式遍历AST树对于遍历到的每条门路,babel-types提供用于批改AST节点的节点类型数据所以,整个Babel底层编译能力由如下局部形成:当咱们理解Babel的底层能力后,接下来看看基于这些能力,下层能实现什么性能? Babel的下层能力基于Babel对JS代码的编译解决能力,Babel最常见的下层能力为: polyfillDSL转换(比方解析JSX)语法转换(比方将高级语法解析为以后可用的实现)因为篇幅无限,这里仅介绍polyfill与「语法转换」相干性能。polyfill作为前端,最常见的Babel生态的库想必是@babel/polyfill与@babel/preset-env。 应用@babel/polyfill或@babel/preset-env能够实现高级语法的降级实现以及API的polyfill。 从上文咱们晓得,Babel自身只是JS的编译器,以上两者的转换性能是谁实现的呢? 答案是:core-js core-js简介core-js是一套模块化的JS规范库,包含: 始终到ES2021的polyfillpromise、symbols、iterators等一些个性的实现ES提案中的个性实现跨平台的WHATWG / W3C个性,比方URL从core-js仓库[3]看到,core-js也是由多个库组成的Monorepo,包含: core-js-buildercore-js-bundlecore-js-compatcore-js-purecore-js咱们介绍其中几个库:core-jscore-js提供了polyfill的外围实现。 import 'core-js/features/array/from'; import 'core-js/features/array/flat'; import 'core-js/features/set'; import 'core-js/features/promise'; Array.from(new Set([1, 2, 3, 2, 1])); // => [1, 2, 3][1, [2, 3], [4, [5]]].flat(2); // => [1, 2, 3, 4, 5]Promise.resolve(32).then(x => console.log(x)); // => 32间接应用core-js会净化全局命名空间和对象原型。 ...

June 26, 2021 · 2 min · jiezi

关于babel:Babel-Polyfills-有-s

Babel polyfill 和 Babel polyfills 就一个 s 之遥,前者是已被弃用的旧时 Babel 官网基于 regenerator-runtime 和 core-js 保护的 polyfill,后者是仍在测试的当初 Babel 官网保护的 polyfill 抉择 - 策略 - 插件 - 集。 相较于保护本人的 polyfill,Babel 更专一于提供更为灵便的 polyfill 抉择策略。 以后,@babel/preset-env 反对指定指标浏览器,通过 useBuiltIns 提供 entry 和 usage 两种注入模式;@babel/plugin-transform-runtime 不净化全局作用域,复用辅助函数为库开发者减小 bundle 体积。 然而,这两个组件并不能很好地配合应用,二者的 polyfill 注入模式只能任选其一。另外,它们只反对 core-js,有很大的局限性。 Babel 社区在 历时一年的探讨 后,设计开发 Babel polyfills 作为这些问题的对立解决方案。它同时: 反对指定指标浏览器;反对不净化全局作用域;反对配合 @babel/plugin-transform-runtime 复用辅助函数;反对 core-js 和 es-shims,并反对、激励开发者写本人的 polyfill provider。致力于对立 Babel 对 polyfill 的抉择策略。Babel polyfills 长处很多,应用是大势所趋。官网的应用文档 写得很清晰,有须要的同学能够点击链接查看。 Exclude应用 Babel 很容易引入 “不太须要的” polyfill,使得打包后的库大小剧增。 ...

June 23, 2021 · 1 min · jiezi

关于babel:开源项目存活有多难-被数百万人使用的-Babel-陷入财务困境

近日,Babel 我的项目外围团队公布博客称陷入财务窘境。为了持续保护和开发 Babel,保障外围开发团队的薪资,Babel 团队向社区申请反对,心愿收到捐献或资助。 据走漏,Babel 我的项目自 2018 年起,进行了一项资金试验(funding experiment):全职从事 Babel 开发是否可继续?目前的状况看来,这项试验失败了。 Babel 我的项目简介Babel 是一个工具链,次要用于将采纳 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便可能运行在以后和旧版本的浏览器或其余环境中。Babel 可能实现以下性能: 语法转换通过 Polyfill 形式在指标环境中增加缺失的个性(通过第三方 polyfill 模块,例如 core-js,实现)源码转换 (codemods)Babel 的用户有数百万人,被寰球成千上万家公司应用,每月下载量超过 1.17 亿次。目前 Babel 已被集成到 JavaScript 生态的各种框架中,包含 React、Next.js、Vue、Ember、Angular 等。此外,它反对各种场景下的自定义插件和优化,如 CSS-in-JS、GraphQL 或跨微小代码库的本地化。 目前,Babel 实现了对许多新 ECMAScript 提案的反对,与每个 TypeScript 和 Flow 新版本放弃同步,并设计了一些新性能以容许生成较小的编译输入。Babel 团队示意行将公布下一个次要版本 Babel 8,并公布了路线图(详见 https://babeljs.io/docs/en/ro... 全职开发 Babel,有多难?只管用户泛滥,但 Babel 我的项目的资金状况并不乐观。 每个我的项目都面临着独特的资金问题。对于 Babel 来说,用户通常不会间接与之交互:像大多数构建工具一样,用户只在最后设置一次,之后就遗记了(除非有时发现错误)。 即便大多数次要的 JS 框架集成了 Babel,但开发者通常不会看到「Babel 开发人员」的工作机会。此外,Next.js 之类的预配置框架使用户无需治理底层构建工具,这类框架的倒退使得「应用 Babel,但不理解它」的状况十分常见。这为吸引资助减少了难度。 2018 年,Babel 现任负责人、外围维护者之一 Henry Zhu 辞去工作,全职从事 Babel 我的项目开发工作。通过数月的筹款之后,Henry 以每月 11000 美元的根底薪资投入 Babel 我的项目中。 ...

May 13, 2021 · 1 min · jiezi

关于babel:ES6转换成ES5

一、 介绍 ECMAScript 6(ES6)的倒退速度十分之快,但古代浏览器对ES6新个性反对度不高,所以要想在浏览器中间接应用ES6的新个性就得借助别的工具来实现。 Babel是一个宽泛应用的转码器,babel能够将ES6代码完满地转换为ES5代码,所以咱们不必等到浏览器的反对就能够在我的项目中应用ES6的个性。babel只是转译新规范引入的语法,比方ES6的箭头函数转译成ES5的函数;而新规范引入的新的原生对象,局部原生对象新增的原型办法,新增的API等(如Proxy、Set等),这些babel是不会转译的。须要用户自行引入polyfill来解决请移步深刻了解Babel原理及其应用 二、应用1、新建工程文件夹这里起名叫做es6,而后在外面创立两个文件夹别离为src 、js如下图:(src为待转换es6 js寄存目录,js为编译实现后的es5 js寄存目录) 2、在src目录下新建一个js文件(这里起名叫做index.js),外面输出es6的代码: let b = 1;console.log(b);const name = '张三';console.log(name);let c = '胜利了么';setTimeout(() => { console.log(c)}, 200)3、 初始化我的项目 1)关上终端命令提示符 进入工程目录(这里也就是es6文件夹)输出如下命令初始化我的项目:(这里用的npm,国内用户倡议用cnpm不懂得能够移步至淘宝镜像应用)命令执行实现后会在根目录生成package.json文件。 npm init -y //-y是指示意全副默认,不须要一个一个敲回车2)关上咱们能够看到外面的内容(能够依据本人的须要进行批改,比方咱们批改name的值。) {  "name": "es6",  "version": "1.0.0",  "description": "",  "main": "index.js",  "scripts": {    "test": "echo "Error: no test specified" && exit 1"  },  "author": "",  "license": "ISC"}三、全局装置babel工具1)在终端中输出以下命令, npm install -g babel-cli2)尽管曾经装置了babel-cli,只是这样还不能胜利进行转换,咱们还须要装置转换包能力胜利转换。 npm install --save-dev babel-preset-es2015 babel-cli3)装置实现后,咱们能够看一下咱们的package.json文件,曾经多了devDependencies选项。 {  "name": "es6",  "version": "1.0.0",  "description": "",  "main": "index.js",  "scripts": {    "test": "echo "Error: no test specified" && exit 1",    "dev":"babel src --out-dir js"  },  "author": "",  "license": "ISC",  "devDependencies": {    "babel-cli": "^6.26.0",    "babel-preset-es2015": "^6.24.1"  }} 四、新建.babelrc在我的项目根目录新建(.babelrc)文件输出如图所示代码: ...

January 11, 2021 · 1 min · jiezi

关于babel:babel-与-ast

什么是 babelBabel 是一个工具链,次要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便可能运行在以后和旧版本的浏览器或其余环境中。什么是形象语法树(AST)在计算机科学中,形象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种形象示意。 它以树状的模式体现编程语言的语法结构,树上的每个节点都示意源代码中的一种构造。 之所以说语法是“形象”的,是因为这里的语法并不会示意出实在语法中呈现的每个细节。对于 AST 的相干介绍:举荐的介绍链接:Leveling Up One’s Parsing Game With ASTs 中文翻译: https://segmentfault.com/a/11...维基百科里的介绍: https://en.wikipedia.org/wiki... babel 的简略应用相干 api 能够参考文档: https://babeljs.io/docs/en/ba... 应用 babel 的 API 将代码解析成 ast: var babel = require("@babel/core");const code = `const a = 1 + 2;`// code 解析成 astconst result = babel.transformSync(code, {ast: true});console.log(result.ast)当然 ast 也能够转换成代码: const { code } = babel.transformFromAstSync(result.ast, { presets: ["minify"], babelrc: false, configFile: false,});console.log(code)在这个在线网站,你能够更加间接地看到 code 和 ast 的比拟:https://lihautan.com/babel-as...const n = 1 的 ast: ...

December 19, 2020 · 2 min · jiezi

关于babel:按需导入之babel插件转换

为了利用可能快速访问, 须要对构建代码进行"减肥", 将无用代码剔除掉.以后得支流构建框架webpack和rollup等都提供了tree shaking机制, 利用es6得申明式模块零碎语法和语句依赖剖析, 进行高精度得代码剔除. 但tree shaking也存在一些限度, 个别的第三方库都采纳es5语法, 不应用es6的模块语法,导致tree shaking生效.对于这些第三方库, 个别采纳一些转换导入语句的babel插件, 如 babel-plugin-import), babel-plugin-component), babel-plugin-transform-imports)等. 本文中的案例中很多采纳了antd的例子, 但其实antd是反对tree shaking的, 不须要应用这些插件也能按需导入.导入语句转换插件不采纳按需导入的插件, 导入一个库如loadash, 就会这样写: import { trim, isEqual } from 'loadash';trim(str);isEqual(1, 2);这会将整个lodash代码都给导入, 如果不像导入不须要的代码, 且以后库反对按需导入, 手动按需导入的代码应该为: import isEqual from 'lodash/isequal';import trim from 'lodash/trim';trim(str);isEqual(1, 2);但以后模块中大量应用了这个库的模块(函数)时, 手动按需导入就会十分繁琐, 代码整洁度大大降低了. 如果可能将全量导入代码: import { trim, isEqual } from 'loadash';利用工具转换成按需导入代码: import isEqual from 'lodash/isequal';import trim from 'lodash/trim';这样既能享受全量导入的简洁, 又能够不必放心导入过多的无用代码. 而babel-plugin-import, babel-plugin-component, babel-plugin-transform-imports等就是这种提供转换的工具. babel-plugin-import是阿里为了antd组件库量身定做的一套转换工作, 不过在后续的更新中, 实用的范畴越来越广. 它除了会导入指标组件外, 还反对导入组件从属款式文件. 转化示意: ...

October 25, 2020 · 2 min · jiezi

关于babel:再学babel配置

babel 每次学习都有新的了解,哪怕是其配置都与咱们前端生态中的各种概念非亲非故。近期再次温习babel常识从而更好的编写js类库,本文是学习过程所做的记录。 babel 生态里的一些npm模块babel/core 外围本义性能babel/cli 命令行工具,能够通过babel命令来转换代码,或者转换文件,输入本义后的文件。如 babel src --out-dir lib --watchbabel/node 是用他来执行es6+代码,以node形式执行。适宜于去执行你写的nodejs代码。那么,咱们应用 webpack 来打包js代码时,是应用的什么呢?答案是 babel-loader。 babel-lodaer 是适配 webpack 的一个模块,其外部就是调起 babel/core 来实现代码转译。 了解转译其实有2种类型的转译记住:babel本身惟一能做的事件叫:代码转译。至于新增api那些,那是须要引入polyfill垫片的事件(下文会讲)。 babel自身所做的事件,个别都叫他代码转译。那么所谓的转译都有哪些品种呢?如下: 1、单纯的语法解析和转换。例如箭头函数转成function2、须要一点辅助的语法解析和转换,例如 class语法,给你转成prototype原型语法 解释下第二点:咱们晓得,一种新的语法意味着新的一些关键字和符号等,那么对于一些特殊符号如箭头函数,那么他恰好有es5对应的 function 关键字,所以babel只需简略换一下字符。 然而有些却不是简略的换一下,例如 class 语法,babel给你转成 es5 之后,他必须革新成函数和prototype的写法,在这种状况下,babel编译后的代码中会退出一些辅助函数(也就是下文说的babel/runtime所干的事儿)以帮助来用es5的形式实现 class。 举个栗子: 看到了吗,babel生成的原型式写法中,须要在构造函数中对调用者的实例化形式进行检测,这些都封装成了 helper函数 _classCallCheck。 除此之外,还有更简单的场景 babel转译之后甚至须要依赖polyfill垫片:例如 async 转换。 babel 会把 async 语法代码转成 generator 辅助函数,而 generator 辅助函数的性能须要依赖 regenerator-runtime 这个polyfill垫片。如下图,babel给你把async语法转换后,多了很多辅助函数;甚至其中有一个 regeneratorRuntime 的函数是没有找到定义的,而这个函数其实就是依赖全局须要引入 regenerator-runtime 这个 polyfill 才行。 了解什么是 corejs 和 babel/runtime首先,咱们要了解什么是 polyfill 什么是辅助函数。 像上文所说的2种babel转译,其中转换之后所呈现的那些辅助函数,叫做运行时所须要的helper,他们其实就是 runtime。 而这些运行时函数,有一个独自的 npm 包去实现他们,叫做 babel/runtime。 ...

October 24, 2020 · 2 min · jiezi

关于babel:通过编译器插件实现代码注入

原文:通过编译器插件实现代码注入 | AlloyTeam作者:林大妈 背景问题大型的前端零碎个别是模块化的。每当发现问题时,模块负责人总是要反复地在浏览器中找出对应的模块,略读代码后在对应的函数内打上断点,最终开始排查。 大部分状况下,咱们会抉择在固定的地位(例如模块的入口,或是类的构造函数)打上断点。也就意味着打断点的过程对于开发者来说是机械的劳动。那么有没有方法在不污染源代码的根底上通过配置来为代码打上断点呢? 实现思路要想不污染源代码,只能抉择在编译时进行解决,能力将想要的内容注入到指标代码中。代码编译的基本原理是将源代码解决成单词串,再将单词串组织成形象语法树,最终再通过遍历形象语法树并转换下面的节点而造成指标代码。 因而,代码注入的关键点就在于在形象语法树造成时对语法树节点进行解决。前端代码通常会应用 babel 进行编译。 相熟 babel 的基本原理babel 的组成babel 的外围是 babel-core。babel-core 可被划分成三个局部,别离解决对应的三个编译过程: babel-parser —— 负责将源代码字符串“单词化”并转化成形象语法树babel-traverse —— 负责遍历形象语法树并附加解决babel-generator —— 负责通过形象语法树生成指标代码babel-parser整个 babel-parser 应用继承的形式,依据性能的不同逐层包装: tokenizerbabel-parser 的一个外围是“tokenizer”,能够了解为“单词生成器”。babel 保护了一个 state(一个全局的状态),它会通过记录一些元信息提供给编译器,例如: “这段 JavaScript 代码是否应用了严格模式?”“咱们当初辨认到第几行第几列了?”“这段代码里有哪些正文?”tokenizer 的外部定义了不同的办法以辨认不同的内容,例如: 读到引号时通过 readString 办法尝试生成一个字符串 token读到数字时通过 readNumber 办法尝试生成一个数字 tokenLVal/Expression/StatementParserbabel-parser 的另一个外围是“parser”,能够了解为“语法树生成器”。其中,StatementParser 是子类,当咱们引入 babel-parser 并调用 parse 办法时,辨认过程将从此处启动(babel 应该是将整个文件认为是一个语句节点)。 同样地,这些 parser 的外部也都为辨认不同的内容而定义不同的办法,例如: 辨认到 true 时,生成一个 Boolean 字面量表达式辨认到 @ 这个符号时,生成一个  装璜器语句节点babel-traversebabel-traverse 提供办法遍历语法树。它应用访问者模式,为外界提供增加遍历时附加操作的入口。 在访问者模式(Visitor Pattern)中,咱们应用了一个访问者类,它扭转了元素类的执行算法。通过这种形式,元素的执行算法能够随着访问者扭转而扭转。TraversalContext遍历语法树时,babel 同样定义了一个 context 判断是否须要遍历以及遍历的形式。 TraversalContext 先将节点和外来的访问者进行无效化的解决,而后结构拜访队列,最初启动深度优先遍历整棵语法树的过程。 ...

October 19, 2020 · 2 min · jiezi

关于babel:译Babel-7120-released快来瞧瞧有哪些亮点

前言本文系 Babel 官网 Blog 翻译,原文地址:https://babeljs.io/blog/2020/10/15/7.12.0 注释开始2020 年 10 月 15 日 咱们刚刚公布了 Babel 的新的主要版本! 这次公布包含对新的 TypeScript 4.1 beta features:template literal types 和 key remapping in mapped types。 另外,咱们实现了两个新的 ECMAScript 提案:class static blocks 和 imports and exports with string names 过来,咱们将 @babel/plugin-sytax-module-attributes(以及相应地解析器插件 moduleAttributes)重命名为 @babel/plugin-syntax-import-assertions(和 importAssertions),为了匹配近期的提案更新。旧的插件能够在 Babel 8 之前运行,然而当初已启用。 你能够在 GitHub 上浏览残缺的更改日志。 最重要的局部TypeScript 4.1(#12129,#12131)TypeScript 4.1 beta 发表于几星期前,它包含了新性能的类型。 Template Iiteral types 容许应用模板字面量语法在类型级别上连贯字符串: type Events = "Click" | "Focus";type Handler = { [K in `on${Events}`]: Function};const handler: Handler = { onClick() {}, // Ok onFocus() {}, // Ok onHover() {}, // Error!};和 key remapping in mapped types 一起,他们能够用于标识复制的对象转化: ...

October 17, 2020 · 2 min · jiezi

关于babel:Babel-入门指引

译文:Babel.js Guide -Part 1- The Absolute Must-Know Basics: Plugins, Presets, And Config 本文将讲述:Babel 是什么以及怎么在日常开发中应用它?presets and plugins 是什么及区别,在babel执行中,他们的执行程序是什么?尽管原文题目看似是一个系列,但作者仿佛没有持续,但我曾经想好了下一部分要写的内容;非专业翻译,夹带本人了解,有小改变。 Babel 是什么babel 是一个收费开源的JavaScript 编译库. 它依据你的配置将代码转化成各式各样的JS代码。 最常见的应用形式就是将古代语法JavaScript es6+编写的代码 转化成 es5,从而兼容更多的浏览器(特地是IE),上面以Babel 转换es6 箭头函数 为 es5 函数的为例。 // The original codeconst foo = () => console.log('hello world!');转移后 // The code after babel transpilationvar foo = function foo() { return console.log('hello world!');};你能够在这里在线尝试 应用Babel在线Repl这是应用Babel的最简略办法。这兴许不是一个十分实用的工具,然而是最快的测试或者试验Babel如何工作的工具, 在线Repl地址。 构建库应用Babel的最风行办法是应用Webpack,Gulp或Rollup等构建库进行打包构建。每个形式都应用Babel作为构建的一部分来实现本人的构建过程。 比方,咱们最罕用的webpack: { ... module: { rules: [ { test: /\.js$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', options: { plugins: ['@babel/plugin-transform-arrow-functions'] } } } ] }, ...}库罕用的构建工具:Rollup ...

August 17, 2020 · 5 min · jiezi

暂别babel

最近2天几乎不停歇地在看关于babel的东西之前对知道是一窍不通的,有时候经常在项目里看见 .babelrc的文件,很是陌生,完全不知道是怎么回事 通过这两天的攻坚学习,终于了解了其实我的目标是了解为什么 babel结合 webpack后不能很好地做到 treeshaking 接下来继续努力吧 还有就是了解了es5是如何实现 async/await了

September 10, 2019 · 1 min · jiezi

关于babelpresetenv以及useBuiltIns配置项

以最新版本 7.5.0为准 安装 npm install --save-dev @babel/core @babel/cli @babel/preset-envnpm install --save @babel/polyfill运行时依赖安装 @babel/polyfill 模块包括 core-js 和一个自定义的 regenerator runtime 模块 npm install --save @babel/polyfill 这个@babel/preset-env套餐几乎能做一切事情,包括处理 polyfills: this option configures how @babel/preset-env handles polyfills.useBuiltIns: 'entry'NOTE: Only use require("@babel/polyfill"); once in your whole app. Multiple imports or requires of @babel/polyfill will throw an error since it can cause global collisions and other issues that are hard to trace. We recommend creating a single entry file that only contains the require statement. ...

September 10, 2019 · 1 min · jiezi

babelpresetsenv配置项-exclude-的探究

babel-presets-env 结合一个target选项很好的控制了 编译的范围。但是有时候,不想制定这个一个不太准确的targrt,而希望更精致的配置,参考文档,就得使用 exclude了 比如,不希望编译 箭头函数,就在exclude里加上 "transform-es2015-arrow-functions", { "presets": [ [ "env", { "modules": false, "useBuiltIns": false, "exclude": [ "transform-es2015-arrow-functions", "transform-async-generator-functions" ] } ]]} 但是如果不想编译 async语法呢? 用这个吗 "transform-async-generator-functions" 报错了:Invariant Violation: Invalid Option: The plugins/built-ins 'transform-async-generator-functions' passed to the 'exclude' option are not valid. Please check data/[plugin-features|built-in-features].js in babel-preset-env (While processing preset: "D:\\learn\\es6\\node_modules\\babel-preset-env\\lib\\index.js")顺着报错行找下去,法相选项只要不在validIncludesAndExcludes 的,统统报错: var validIncludesAndExcludes = [].concat( Object.keys(_plugins2.default), // "../data/plugins.json" // "./module-transformations" Object.keys(_moduleTransformations2.default).map(function(m) { return _moduleTransformations2.default[m]; }), Object.keys(_builtIns2.default), // ../data/built-ins.json _defaultIncludes.defaultWebIncludes // "./default-includes");

September 9, 2019 · 1 min · jiezi

Babel插件开发入门示例详解

Babel简介Babel是Javascript编译器,是种代码到代码的编译器,通常也叫做『转换编译器』。 Babel的工作过程Babel的处理主要过程:解析(parse)、转换(transform)、生成(generate)。 代码解析词法分析和语法分析构造AST。代码转换处理AST,处理工具、插件等就是在这个阶段进行代码转换,返回新的AST。代码生成遍历AST,输出代码字符串。所以我们需要对AST有一定了解才能进行Babel插件开发。 AST在这整个过程中,都是围绕着抽象语法树(AST)来进行的。在Javascritp中,AST,简单来说,就是一个记录着代码语法结构的Object。感兴趣的同学可到https://astexplorer.net/ 去深入体验比如下面的代码: import {Button} from 'antd';import Card from 'antd/button/lib/index.js';转换成AST后如下, { "type": "Program", "start": 0, "end": 253, "body": [ { "type": "ImportDeclaration", "start": 179, "end": 207, "specifiers": [ { "type": "ImportSpecifier", "start": 187, "end": 193, "imported": { "type": "Identifier", "start": 187, "end": 193, "name": "Button" }, "local": { "type": "Identifier", "start": 187, "end": 193, "name": "Button" } } ], "source": { "type": "Literal", "start": 200, "end": 206, "value": "antd", "raw": "'antd'" } }, { "type": "ImportDeclaration", "start": 209, "end": 253, "specifiers": [ { "type": "ImportDefaultSpecifier", "start": 216, "end": 220, "local": { "type": "Identifier", "start": 216, "end": 220, "name": "Card" } } ], "source": { "type": "Literal", "start": 226, "end": 252, "value": "antd/button/lib/index.js", "raw": "'antd/button/lib/index.js'" } } ], "sourceType": "module"}插件开发思路确定我们需要处理的节点类型处理节点返回新的节点简单插件结构插件必须是一个函数,根据官方文档要求,形式如下: ...

August 7, 2019 · 2 min · jiezi

Babel-7使用总结

Babel 7使用总结 2019-07-08 本文基于Babel 7.4.5。 Babel主要模块如上图所示,接下来将分别介绍。 1. @babel/core@babel/core主要是进行代码转换的一些方法,可以将源代码根据配置转换成兼容目标环境的代码。 import * as babel from "@babel/core";babel.transform("code();", options, function(err, result) { result.code; result.map; result.ast;});2. @babel/cli@babel/cli是 babel 提供的命令行工具,用于命令行下编译源代码。 首先安装依赖: npm install --save-dev @babel/core @babel/cli新建一个js文件: let array = [1,2,3,4,5,6];array.includes(function(item){ return item>2;})class Robot { constructor (msg) { this.message = msg } say () { alertMe(this.message) }}Object.assign({},{ a:1,b:2})const fn = () => 1;new Promise();执行转换: npx babel index.js --out-file out.js可以发现输出代码没有变化,这是因为没有进行配置来确定怎么进行转换。 3. @babel/plugin*babel是通过插件来进行代码转换的,例如箭头函数使用plugin-transform-arrow-functions插件来进行转换。 首先安装该插件: ...

July 10, 2019 · 5 min · jiezi

以何种方式使用babel6种

以何种方式使用babel?(6种) 【01】Babel可以配置!许多其他工具也有类似的配置:ESLint(.eslintrc),Prettier (.prettierrc)。 允许使用所有Babel API 但是,如果该选项需要JavaScript,则可能需要使用JavaScript 【02】babel自身使用的配置文件地址:https://github.com/babel/babe... 以何种方式使用babel? 01、babel.config.js 在项目的根目录中创建一个babel.config.js文件。使用以下内容。 module.exports = function (api) { api.cache(true); const presets = [ ... ]; const plugins = [ ... ]; return { presets,plugins};}复制复制代码 【】例子: 以下的浏览器列表只是一个随意的例子。您必须根据要支持的浏览器进行调整。 const presets = [ [ "@babel/env",{ targets: { edge: "17", firefox: "60", chrome: "67", safari: "11.1", }, useBuiltIns: "usage",},],]; module.exports = { presets };复制代码 运行此命令将src目录中的所有代码编译为lib目录: ./node_modules/.bin/babel src --out-dir lib复制复制代码 02、.babelrc 在项目的根目录中创建一个.babelrc文件。使用以下内容。 一个仅适用于简单单个包的静态配置。 { "presets": [...], "plugins": [...]}复制复制代码 ...

July 8, 2019 · 1 min · jiezi

使用storybook搭建私有组件库

(一)私有组件库搭建汇总既有项目中的通用组件项目地址 为什么搭建私有组件库可复用 跨项目可以使用同一套私有组件库方便维护 如需组件调整 只需要修改组件库 不需要跨项目重复修改添加新组件原则组件应先存在于具体项目中,经过重复验证后再抽象、沉淀到本组件库中不涉及国际化、ajax 请求等业务逻辑项目构建1.新建 package.jsoncd XXX[项目名]npm init2.Storybook for Vue参考文档Storybook for Vue 可以选择自动搭建 storybook 项目也可以使用手动搭建 自动搭建npx -p @storybook/cli sb init --type vue手动搭建安装依赖# 安装storybooknpm install @storybook/vue --save-dev# 安装vue 以及需要的loadernpm install vue --savenpm install vue-loader vue-template-compiler @babel/core babel-loader babel-preset-vue --save-dev在 package.json 添加启动项{ "scripts": { "storybook": "start-storybook" }}创建 storybook 的配置文件import { configure } from '@storybook/vue'function loadStories() { require('../stories/index.js') // You can require as many stories as you need.}configure(loadStories, module)创建测试文件 ./stories/elButton.stories.js// 引入了 element-ui 可以针对 el-button 组件测试项目是否正常import Vue from 'vue'import { storiesOf } from '@storybook/vue'storiesOf('elButton', module) .add('with text', () => '<el-button>with text</el-button>') .add('with emoji', () => '<el-button>???? ???? ???? ????</el-button>') .add('as a component', () => ({ template: '<el-button :disabled="true">rounded</el-button>' }))运行查看效果npm run storybook提取私有组件创建测试文件 ./stories/changeTime.vue<template> <el-dialog :title="title" :visible.sync="show" width="400px" class="change-time-dialog" :close-on-click-modal="false" :before-close="handleClose" > <el-form :model="form" ref="form" style="margin-bottom:30px"> <el-form-item style="margin-bottom:0px !important"> <el-date-picker type="date" size="medium" value-format="yyyy-MM-dd" :placeholder="`请选择${title}`" v-model="form.time" style="width:94%" ></el-date-picker> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button size="small" @click="handleClose">取消</el-button> <el-button size="small" type="primary" @click="handleConfirm('form')" :disabled="disable" >确定</el-button > </div> </el-dialog></template><script>export default { data() { return { show: false, form: { time: '' }, disable: false } }, props: { visible: { type: Boolean, default: false }, time: { type: String, default: '' }, typeTime: { type: String, default: 'start' }, title: { type: String, default: '' }, referTime: { // 参考时间 type: [Date, String], default: '' } }, mounted() { this.show = this.visible this.form.time = this.time }, methods: { handleClose() { this.$refs.form.resetFields() this.show = false this.$emit('update:visible', false) this.$emit('update:time', '') }, handleConfirm() { if (this.typeTime === 'start') { if ( new Date(this.form.time).getTime() > new Date(this.referTime).getTime() ) { this.$message.warning('起租时间必须小于等于退租时间') return } } if (this.typeTime === 'end') { if ( new Date(this.form.time).getTime() < new Date(this.referTime).getTime() ) { this.$message.warning('退租时间必须大于等于起租时间') return } } this.$emit('changeTime', this.typeTime, this.form.time, this.clearData) }, clearData() { this.show = false this.disable = false this.$emit('update:visible', false) } }}</script><style lang="scss" scoped>div.el-form-item__error { top: 80% !important;}.change-time-dialog /deep/ .el-dialog__body { padding-bottom: 0px !important;}</style>创建测试文件 ./stories/changeTime.stories.jsimport Vue from 'vue'import { storiesOf } from '@storybook/vue'import changeTime from './changeTime'storiesOf('changeTime', module).add('修改时间', () => ({ components: { changeTime }, template: `<div> <h4>选择时间:{{ curTime }}</h4> <el-button @click="handleChangeTime">选择起租时间</el-button> <change-time :title="changeTimeTitle" :time.sync="curTime" v-if="changeTimeVisiable" :typeTime="changeTimeType" :referTime="referTime" ref="changeTime" :visible.sync="changeTimeVisiable" @changeTime="changeTime" /> </div>`, data() { return { changeTimeTitle: '起租时间', curTime: '2019-06-25', changeTimeType: 'start', changeTimeVisiable: false, referTime: '' } }, methods: { changeTime(type, time, fn) { this.curTime = time fn() }, handleChangeTime() { this.changeTimeVisiable = true } }}))scss 报错,es6 语法报错解决 css 问题// 安装相关loadernpm i -D node-sass less-loader css-loader style-loader配置 webpack.config.js ...

June 28, 2019 · 3 min · jiezi

初学-Babel-工作原理

原文链接:初学 Babel 工作原理 前言 Babel 对于前端开发者来说应该是很熟悉了,日常开发中基本上是离不开它的。 已经9102了,我们已经能够熟练地使用 es2015+ 的语法。但是对于浏览器来说,可能和它们还不够熟悉,我们得让浏览器理解它们,这就需要 Babel。 当然,仅仅是 Babel 是不够的,还需要 polyfill 等等等等,这里就先不说了。 What:什么是 BabelBabel is a toolchain that is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments.简单地说,Babel 能够转译 ECMAScript 2015+ 的代码,使它在旧的浏览器或者环境中也能够运行。 我们可以在 https://babel.docschina.org/repl 尝试一下。 一个小????: // es2015 的 const 和 arrow functionconst add = (a, b) => a + b;// Babel 转译后var add = function add(a, b) { return a + b;}; ...

June 25, 2019 · 5 min · jiezi

vuecli3下配置IE浏览器兼容性

最近,在重构公司官网,需要兼容ie9/10/11,使用vue-cli构建项目。 在查询如何兼容ie时,首先是查看vue cli官网,浏览器兼容性,感觉官网说的不是太明晰(小白一枚,实在不太懂),然后就直接百度了,网上方法基本类似,都是用 browserslist,babel-polyfill等,只是具体步骤有差异,不过 都没有解决本人问题。经过层层查询,逐步对vue-cli 原理了解后,网上查询方法结合本人理解,将问题解决,在此记录下。 先说步骤,然后说一些自己浅显理解。 一.解决步骤1.配置browserslist配置这一项,有两个途径,一是 在 package.json文件中,添加browserslist字段;二是 在项目根目录,创建一个.browserslistrc文件,两种途径。这两种方法不能同时配置,不然运行serve命令会报错。 先说package.json中配置,网上很多如下方法: "browserslist": [ "> 1%", "last 4 versions" ]本人依此配置,报错!然后按照另外一种配置,如下: "browserslist": [ "ie 11" ]成功! 额外贴出.browserslistrc方法配置方式(只是列出书写方式,代码有效性,按上面来): > 1%last 4 versions2.安装babel-polyfill依赖这个简单,直接 npm i babel-polyfill --save-dev即可 3.引入babel-polyfill依赖 这一步是关键,网上查询很多,在main.js中,import babel-polyfill引入,本人测试后无效。 具体引入是在vue.config.js中configureWebpack字段中,具体: configureWebpack: config => { config.entry.app = ["babel-polyfill", "./src/main.js"]; }即在webpack入口配置中,加入babel-polyfill。至此,在ie下,兼容es6就解决了! 二.自己的理解上面把问题解决了,再将自己解决过程中的理解梳理下。 1.首先上面的解决步骤,仅仅适用于源码(/src),对依赖包无效。当需要对依赖包做兼容转译时,就需要用到官网中Polyfill部分内容了 2.vue-cli中webpack配置文件是在node_modules中的,没有直接在根目录,要想配置webpack,需要在vue.config.js中configureWebpack字段或者chainWebpack配置。具体如下: module.exports = { configureWebpack: { resolve: { alias: { querystring: 'querystring-browser' } } }}module.exports = { chainWebpack: config => { config.resolve.alias.set('querystring', 'querystring-browser') }}解决过程中,查询的相关资料1.Vue 兼容 ie9 的全面解决方案2.Vue CLI 3 浏览器兼容性配置3.Editing webpack.config.js when using vue-cli 3 ...

June 19, 2019 · 1 min · jiezi

从webpack初始化到配置babel环境

一、新建项目文件夹,在文件夹打开终端运行npm init,一直回车二、安装babel所需要的包 npm install --save-dev @babel/core @babel/cli @babel/preset-envnpm install --save @babel/polyfill三、根目录下新建babel.config.js,填入: const presets = [ [ "@babel/env", { targets: { edge: "17", firefox: "60", chrome: "67", safari: "11.1", }, useBuiltIns: "usage", }, ],];module.exports = { presets };四、根目录下新建webpack.config.js,填入: const path = require('path');module.exports = { entry: './src/index.js', output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist') }, mode: 'development'};五、执行,安装webpack,根据提示安装webpack-cli npm install webpack --save-dev 六、修改packge.json,script填入 "build": "webpack"七、命令行运行,npm run build

June 13, 2019 · 1 min · jiezi

解决vue项目在IE浏览器运行打开空白使用babelpolyfilll把ES6转ES5

IE的话一般需要将ES6转ES5。下面转换方法:需要安装babel-polyfill npm install --save babel-polyfill然后在main.js引入该插件 最后需要在webpack配置入口即可 app: ['babel-polyfill', './src/main.js'] 最后,重启一下项目 npm run dev

June 13, 2019 · 1 min · jiezi

babel插件汇总

babel-plugin-transform-builtin-extendbabel-plugin-transform-es2015-classes对类继承的编译存在兼容性问题

June 12, 2019 · 1 min · jiezi

babel7-重新理解

测试环境node 10.14.1Babel 7.4.3 Babel 是什么?Babel 是一个工具链,主要用于将 ECMAScript2015+版本的代码转换为向后兼容的 Javascript 代码,以便能够运行在当前和旧版本的浏览器或其他环境中。 Babel 主要功能点:语法转换垫片兼容处理,通过 Polyfill 方式在目标环境中添加缺失的特性源码转换其他...使用理念Babel 主要通过 插件 plugins 的形式 达到转换代码的目的。Babel 本身内置了部分环境预设 preset-env,当然项目中 需要根据实际 进行配置。为了方便书写参数,一般通过 配置文件的方式 babel.config.js 或者.babelrc.js(以编程的方式创建配置)或者.bablercBabel 编译两大核心- 语法转换- 垫片支持Bable 核心模块@babel/coreBabel 语法转换核心功能,内置 helpers 插件模块,是语法转换的主要辅助工具 @babel/preset-env这是一个非常智能的环境预设模块,根据 env 配置自动 分析查找对应的 helper 和 plugins 进行代码编译转换基本使用: presets: [ [ '@babel/preset-env', { targets: { chrome: '77', android: '2', }, debug: true, useBuiltIns: false, }, ],],其他参数参考文档配置就好了,这里重点分析一下 useBuiltIns 属性.targets 可根据文档自行配置,这个比较简单。debug 调试模式,建议开启,开启之后可以看到 那些 target 使用了那些 plugins 和 polyfill ...

June 3, 2019 · 2 min · jiezi

还学不会webpack看这篇

Webpack已经流行好久了,但很多同学使用webpack时还是一头雾水,一下看到那么多文档、各种配置、各种loader、plugin立马就晕头转向了。我也不例外,以至于很长一段时间对webpack都是一知半解的状态。但是想要继续做好前端,webpack是必须得跨过的一道坎,其实掌握webpack并不难,只是我们没有找到正确的方法。本文就是我自己在学习webpack时的一点心得体会,供大家参考。 什么是webpack?一句话概括:webpack是一个模块打包工具(module bundler)。重点在于两个关键词“模块”和“打包”。什么是模块呢?我们回顾一下曾经的前端开发方式,js文件通过script标签静态引入,js文件之间由于没有强依赖关系,如果文件1要用到文件2的某些方法或变量,则必须将文件1放到文件2后面加载。随着项目的增大,js文件之间的依赖关系越发错综复杂,维护难度也越来越高。这样的困境驱使着前端工程师们不断探索新的开发模式,从后端、app的开发模式中我们获得灵感,为什么不能引入“模块”的概念让js文件之间可以相互引用呢?模块1要使用模块2的功能,只需要在该模块1中明确引用模块2就行了,而不用担心它们的排列顺序。基于这种理念,CommonJS和 AMD规范被创造了出来,然后有了require.js、system.js这样的前端模块加载工具和node的模块系统,直到现在流行的es6 module。 模块的引入解决了文件之间依赖引用的问题,而打包则解决了文件过多的问题。当项目规模增大,模块的数量数以千计,浏览器如果要加载如此多的文件,页面加载的速度势必会受影响,而bundler可以把多个关联的文件打包到一起从而大量减少文件的数量提高网页加载性能。提供模块化的开发方式和编译打包功能就是webpack的核心,其他很多功能都围绕它们展开。 核心概念Module(模块)对于webpack,模块不仅仅是javascript模块,它包括了任何类型的源文件,不管是图片、字体、json文件都是一个个模块。Webpack支持以下的方式引用模块: ES2015 import 方法CommonJs require() 方法AMD define 和 require 语法css/sass/less文件中的 @import 语法url(...) 和 <img src=...> 中的图片路径Dependency Graph(依赖关系图)所谓的依赖关系图是webpack根据每个模块之间的依赖关系递归生成的一张内部逻辑图,有了这张依赖关系图,webpack就能按图索骥把所有需要模块打包成一个bundle文件了。 Entry(入口)绘制依赖关系图的起始文件被称为entry。默认的entry为 ./src/index.js,或者我们可以在配置文件中配置。entry可以为一个也可以为多个。 单个entry:module.exports = { entry: './src/index.js'}或者 module.exports = { entry: { main: './src/index.js' }};多个entry,一个chunk我们也可以指定多个独立的文件为entry,但将它们打包到一个chunk中,此种方法被称为 multi-main entry,我们需要传入文件路径的数组: module.exports = { entry: ['./src/index.js', './src/index2.js', './src/index3.js']}但是改种方法的灵活性和扩展性有限,因此并不推荐使用。 多个entry,多个chunk如果有多个entry,并且每个entry生成对应的chunk,我们需要传入object: module.exports = { entry: { app: './src/app.js', admin: './src/admin.js' }};这种写法有最好的灵活性和扩展性,支持和其他的局部配置(partial configuration)进行合并。比如将开发环境和生产的配置分离,并抽离出公共的配置,在不同的环境下运行时再将环境配置和公共配置进行合并。 Output(出口)有了入口,对应的就有出口。顾名思义,出口就是webpack打包完成的输出,output定义了输出的路径和文件名称。Webpack的默认的输出路径为 ./dist/main.js。同样,我们可以在配置文件中配置output: module.exports = { entry: './src/index.js', output: { path: __dirname + '/dist', filename: 'bundle.js' }};多个entry的情况当有多个entry的时候,一个entry应该对应一个output,此时输出的文件名需要使用替换符(substitutions)声明以确保文件名的唯一性,例如使用入口模块的名称: ...

May 27, 2019 · 2 min · jiezi

babel在提升前端效率的实践

大纲遇到的问题场景及解决方案对比什么是babel?解决过程目前遗留的问题目前实现功能API参考遇到的问题场景及解决方案对比我们目前采用的是antd + react(umi)的框架做业务开发。在业务开发过程中会有较多频繁出现并且相似度很高的场景,比如基于一个table的基础的增删改查,这个相信大家都非常熟悉。在接到一个新的业务需求的时候,相信有不少人会选择copy一份功能类似的代码然后基于这份代码去改造以满足当前业务,当然我目前也是这样做的~ 其实想把这块功能提取成一个公共组建的想法由来已久,最近开始做基础组件,便拿这个下手了。经过一周左右的时间完成了基础组件的编写。 查看基础支持的功能点API。基本的思路是通过json生成一些抽象配置,然后通过解析json的抽象配置+渲染器最终生成页面。json配置涵盖了目前80%的业务场景的基本需求,但是可扩展性很低。比如一些复杂的业务场景:表单的关联校验、数据关联显示、多级列表下钻等等功能。虽然通过一些较为复杂的处理可以把这些功能融入进来,但最终组件将会异常庞大难以维护。 所以,我能不能通过这些json配置通过某种工具生成对应的代码?这样一来以上提到的问题就完全不存在了,因为这和我们自己写的代码完全一样,工具只是帮我们完成初始化的过程。所以后来想了很多办法,最初采用template string的方式,这种方式较为简单粗暴,无非通过string中嵌套变量的判断来输出code。但是在实际写的时候发现很多问题,比如 function的输出(JSON.stringify会将function忽略)多层函数嵌套之后怎么获取最终渲染的节点code嵌入变量怎么实现、umi-models-effects/reducer中额外的字典查询怎么生成等等..最终学习了一些生成代码的工具比如angular-cli以及一些关于js生成代码的文章,主要是通过知乎上的这篇讨论了解到了大家是怎么处理这种问题的。最终决定采用babel的生态链来解决上述遇到的问题。 我们目前采用的方式是基于antd+react(umi)编写通用的CRUD模板,然后通过代码生成器解析json中的配置生成对应的代码,大致的流程是: React --> JavaScript AST ---> Code Generator --> Compiler --> Page 目前功能只是完成了初步版本,待应用在项目中使用一段时间稳定之后将会开源~什么是babel?Babel是一个工具链,主要用于编译ECMAScript 2015+代码转换为向后兼容的可运行在各种浏览器上的JavaScript。主要功能: 语法转换环境中缺少的Polyfill功能源代码转换查看更多Babel功能Understanding ASTs by Building Your Own Babel Plugin 如上提供了babel基本的流程及一篇介绍AST的文章。 我的理解中比如一段string类型code,首先通过babel.transform会将code转为一个包含AST(Abstract Syntax Tree)的Object,同样可以使用@babel/generator将AST转为code完成逆向过程。例如一段变量声明代码: const a = 1;在解析之后的结构为: { "type": "Program", "start": 0, "end": 191, "body": [ { "type": "VariableDeclaration", "start": 179, "end": 191, "declarations": [ { "type": "VariableDeclarator", "start": 185, "end": 190, "id": { "type": "Identifier", "start": 185, "end": 186, "name": "a" }, "init": { "type": "Literal", "start": 189, "end": 190, "value": 1, "raw": "1" } } ], "kind": "const" } ], "sourceType": "module"}首先类型为VariableDeclaration,首先他的类型是const,可以通过点击查看api其它还有let、var的值。其次是声明declarations部分,这里值为数组,因为我们可以同时定义多个变量。数组中值的类型为VariableDeclarator,包含id和init两个参数,分别为变量名称以及变量值。id的类型为Identifier,译为修饰符即是变量名称。init类型为Literal,即是常量,一般常用的有stringLiteral、numericliteral、booleanliteral等。此时即完成了变量赋值的过程。 ...

May 21, 2019 · 3 min · jiezi

babel7实践

前些天使用了 babel7,特来做个笔记。 presets在 babel7 中,已经废弃了之前的阶段性提案,现在统一使用 @babel/preset-env。所以呢,这里倒是省去了一些麻烦。在 webpack 配置中 preset-env 配合 babel-loader 就可以转换 ES2015+ 语法了。同时,官方建议我们使用 targets 设定目标浏览器决定需要兼容的功能。举个例子: module.exports = { presets: [ [ '@babel/preset-env', { targets: { ie: "11" } } ] ]}polyfillpolyfill 在之前都是用来引入新的 api 和功能的。如我们常用的 promise,Object.assign 等。在 babel6 及之前,只需要在入口引入 polyfill 即可。然而这也带来了问题,一次性加载了整个 polyfill,十分冗余。 而用了 babel7 通过简单的配置就可以达成按需加载的目的,我们也不再需要手动引入 polyfill。 useBuiltIns没错,使用这个新的配置项,就可以实现按需加载 polyfill。其值可取以下3个: false值为 false 的时候,相当于没用,这时就得手动引入所有的 polyfill。 entry使用 entry 的时候,也需要手动引入 polyfill,即 import '@babel/polyfill';,同时也引入了所有的 polyfill。这个配置项,总觉得没什么用,如果有老哥知道的话可以在评论区提出一起讨论。 usage值为 usage 的时候,无需引入 polyfill,babel 会自动按需加载需要的功能: { \\... useBuiltIns: 'usage'}如果需要在命令行打印加载项,可以设置 debug: ...

May 15, 2019 · 2 min · jiezi

babel源码分析之一AST生成

前言babel是javaScript编译器,主要用于将ECMAScript2015+版本的代码转化为具有兼容性的较低版本,从而让代码适用于各种环境。它的早期代码从acorn项目中fork出来,后续提供了acorn不具备的一整套的代码解析,转换,生成的功能。 babel代码转换第一步是将源码转化成AST(Abstract Syntax Tree)。本文将借助acorn初期的源码以及ESTree 规范标准,详细讲解其生成AST的工程细节。 功能分析先举个代码转为ast的例子: /* 测试whileStatement*/while(b !== 0){ if (a > b){ a = a - b; }else{ b = b - a; }}console.log(a)转化后的ast结构 实现思路上图的整个树的生成都是由一次次词法,语法解析中递归出来的。一个完整的递归函数逻辑如下: 去除注释,空格,换行。词法分析: 将源码中的字符转化为单词。即解析token,如while(b !== 0){将被识别为的[while,(,b,!==,0,),{]这7个单词。语法分析:通过词法解析出来的单词token来获取statement节点的类型并解析,然后对其中可能含有的expression进行相应的语法解析。解析出其开始的start,结束的end,值value以及值的类型label。索引移到下一位,开启新一轮的递归。以此循环直到将文件字符串读取完毕。 ... while (tokType !== _eof) { const statement = parseStatement(); if (first) { first = false; if (isUseStrict(statement)) { setStrict(true); } } node.body.push(statement); } ... 功能实现单个递归函数的实现的功能如下 去除注释,空格,换行注释分两种: / /,或者 / * /的多行注释以及//的单行注释。 空格分为' ' t n r f ...

April 28, 2019 · 3 min · jiezi

你对项目里的依赖包了解吗

注意:本文所有依赖包是目前最新版本的 现在很多开发朋友对于使用webapck、babel搭建开发环境已经不陌生,但很少去系统性的了解项目依赖。 本文从环境依赖包说起,让你对自己的开发环境有更深的了解。 为了简单,我们将依赖分个类:Babel相关????、Webpack相关????、可选的依赖包。注意:带???? 是指必需的依赖, 下面我们一个一个来说。 Babel相关????要使用最新的ES6+语法,必须少不了Babel转码,那么要搭建一个完全体的环境,应该使用哪些依赖呢? 首先,我们安装最核心的依赖: @babel/cli、@babel/core、@babel/polyfill、@babel/register、core-js 下面是他们的一些简单解释: { /* Babel 自带了一个内置的 CLI 命令行工具,可通过命令行编译文件。 */ "@babel/cli": "^7.4.3", /* 看到`core`就知道它是`babel`的核心,一些转码操作都是基于它完成的, 所以它是必须的依赖。 */ "@babel/core": "^7.4.3", /* Babel默认只转换新的JavaScript语法,但是不转换新的API,比如 `Iterator`、`Generator`、`Set`、`Maps`、`Proxy`、`Reflect`、 `Symbol`、`Promise` 等全局对象,以及一些定义在全局对象上的方法(比 如 `Object.assign` )都不会转码。而`@babel/polyfill`就可以做到。 */ "@babel/polyfill": "^7.4.3", /* 让webpack.config.babel.js也支持ES6语法 */ "@babel/register": "^7.4.0", /* 通俗说就是动态polyfill,它可以动态加载需要的新API,具体可以看https://github.com/zloirock/core-js#readme */ "core-js": "3", }下面我们安装必需的preset和plugin:@babel/preset-env、@babel/plugin-proposal-class-properties、@babel/plugin-proposal-decorators、@babel/plugin-proposal-object-rest-spread、@babel/plugin-syntax-dynamic-import 下面是它们的一些解释: { /* 根据指定环境来转码,这个不用说,必装 */ "@babel/preset-env": "^7.4.3", /* 对class中属性初始化语法、static等语法进行处理 */ "@babel/plugin-proposal-class-properties": "^7.4.0", /* 装饰器语法处理 */ "@babel/plugin-proposal-decorators": "^7.4.0", /* 对象rest、spread语法处理 */ "@babel/plugin-proposal-object-rest-spread": "^7.4.3", /* import()语法处理 */ "@babel/plugin-syntax-dynamic-import": "^7.2.0",}安装好了以上preset和plugins,我们需要新建一个.babelrc文件来使用它们: ...

April 27, 2019 · 2 min · jiezi

minipack源码解析以及扩展

前置知识首先可能你需要知道打包工具是什么存在基本的模块化演变进程对模块化bundle有一定了解了解babel的一些常识对node有一定常识常见的一些打包工具如今最常见的模块化构建工具 应该是webpack,rollup,fis,parcel等等各种各样。 但是现在可谓是webpack社区较为庞大。 其实呢,模块化开发很大的一点是为了程序可维护性 那么其实我们是不是可以理解为打包工具是将我们一块块模块化的代码进行智能拼凑。使得我们程序正常运行。 基本的模块化演变// 1. 全局函数function module1 () { // do somethings}function module2 () { // do somethings}// 2. 以对象做单个命名空间var module = {}module.addpath = function() {}// 3. IIFE保护私有成员var module1 = (function () { var test = function (){} var dosomething = function () { test(); } return { dosomething: dosomething }})();// 4. 复用模块var module1 = (function (module) { module.moduledosomething = function() {} return module})(modules2);// 再到后来的COMMONJS、AMD、CMD// node module是COMMONJS的典型(function(exports, require, module, __filename, __dirname) { // 模块的代码实际上在这里 function test() { // dosomethings } modules.exports = { test: test }});// AMD 异步加载 依赖前置// requireJS示例define('mymodule', ['module depes'], function () { function dosomethings() {} return { dosomethings: dosomethings }})require('mymodule', function (mymodule) { mymodule.dosomethings()})// CMD 依赖后置 // seajs 示例// mymodule.jsdefine(function(require, exports, module) { var module1 = require('module1') module.exports = { dosomethings: module1.dosomethings }})seajs.use(['mymodule.js'], function (mymodule) { mymodule.dosomethings();})// 还有现在流行的esModule// mymodule export default { dosomething: function() {}}import mymodule from './mymodule.js'mymodule.dosomething()minipack的打包流程可以分成两大部分生成模块依赖(循环引用等问题没有解决的~)根据处理依赖进行打包模块依赖生成具体步骤 ...

April 22, 2019 · 5 min · jiezi

像babel那样使用lerna管理你的项目

如何像 babel 那样进行多包管理呢?babel 项目的 packages 里面存放了 babel 旗下的所有包,比如:babel-core。我们在安装 babel 的时候一般不是这样装的:yarn add babel,而是这样安装的:yarn add @babel/babel-core,这是由于 babel 进行了分包管理的缘故,又因为 npm 支持scope packages,所以我们可以这样子来安装包。我们再来看scope packages,npm 支持以@符开头的包名称,把它叫做有范围的包,示例:@somescope/somepackage。但是在发布包的时候,npm 会把这种包当成是你的私有包来进行发布,一般我们是发布不了的,因为 npm 会提示需要登录等等东西,这个时候我们一般就加上一个参数,告诉 npm 我们要发布的这个包是一个公共包:npm publish –access=public,不出意外这个包就可以发布成功了。别人要安装你的包:yarn add @somescope/somepackage,看起来跟安装 babel-core 一致了。但是还差点东西,就是利用 lerna 来进行管理。要达到类似于 babel 那样的管理方式,首先,你需要有scope packages,正好,我们现在有了一个@somescope/somepackage包,现在我们就用 lerna 来管理它。首先,我们在 github 上面新建一个仓库,就叫做:testlerna 吧(使用已有仓库也行),然后克隆到本地,进入到 testlerna 目录,执行:lerna init或者是lerna init -i(表示 packages 下面的包单独使用版本号),这个时候,lerna 就为我们生成了这几个东西:testlerna|-package.json|-lerna.json|-packages/packages 目录就是 lerna 要进行管理的各种包的目录,所有的包都会放在这个目录下面。接着执行:lerna import ../scopepackage(假设咱们的这个@somescope/somepackage包是放在 scopepackage 目录里面的),这样就会把我们刚才发布的那个包导入到 packages 目录里面,这时候目录看起来像这个样子:testlerna|-package.json|-lerna.json|-packages/|–scopepackage/|—package.json|—…做一些修修改改并执行 git commit 后,我们就可以执行lerna publish了,不出意外你的@somescope/somepackage也在 npm 上更新了,原来的那个@somescope/somepackage仓库可以删除掉了,因为我们把它放在 testlerna 项目中进行管理了,这样就跟 babel 那样的管理方式一样了。这种方式是有一丢丢麻烦,因为 npm 默认会把 @ 开头的包当成私有包来看待,就导致了如果不在 npm 上提交一次公共版本,执行lerna publish时就会发布不了,可能lerna publish调用的 npm 命令是npm publish它没有加–access=publish参数。如果你不想这么麻烦,那么有一个办法就是,不用scope package,也就是你的包名不加 @ 前缀,这样你的包就默认是公共的包了,完全可以不用先去提交一次公共版本,才能使用 lerna 来管理你的包,简单多了。可以访问:https://github.com/kybetter/t… 看到这个测试项目这两个是测试时发布的包:https://www.npmjs.com/package…https://www.npmjs.com/package… 如果这篇文章给你带来帮助,可以给我点个赞。 ...

April 9, 2019 · 1 min · jiezi

【webpack4】用不同语言语法编写webpack配置文件

写在前面webpack配置文件默认情况下为webpack.config.js,即用原生js语法书写的配置文件。然而,在我们的项目中,有时候是使用的是其他语言语法进行编程,例如:es6、coffeeScript、typeScript等语言语法。为此,webpack为我们提供了可以采用不同语言语法类型书写配置文件的功能。具体可以支持的文件拓展可以看这里:https://github.com/gulpjs/int…可以看到,webpack为我们提供了丰富多样可供选择的文件拓展。下面介绍一下最常见的webpack配置文件类型:TypeScript1、安装依赖如果想要使用TypeScript来书写webpack配置文件,首先要先安装依赖:npm install –save-dev typescript ts-node @types/node @types/webpack如果需要用到webpack-dev-server,还需要安装:npm install –save-dev @types/webpack-dev-server2、编写webpack配置文件(1)把webpack配置文件的文件名改为:webpack.config.tsTypeScript的文件拓展名为.ts,所以我们需要同时把webpack配置文件的文件名改为.ts拓展名(原来默认为webpack.config.js)当我们把webpack配置文件名拓展改为.ts时,webpack也会自动读取该拓展名下的文件。即不需要这样设置:>> webpack –config webpack.config.tswebpack会自动帮我们读取webpack.config.ts文件,不需要我们再去设置了(2)编写webpack.config.ts配置文件利用TypeScript编写webpack配置文件时,webpack配置文件的结构同以前一样,只不过语言变为Typescript而已。//webpack.config.tsimport path from ‘path’import webpack from ‘webpack’?const config: webpack.Configuration = { entry: ‘./src/index.js’, output: { filename: ‘bundle.js’, path: path.resolve(__dirname, ‘dist’), publicPath: path.resolve(__dirname, ‘dist’), },}export default config在本webpack配置文件webpack.config.ts中,我们把require语法改为ts中的import、export静态模块引入导出的语法,以便我们测试。3、编写TypeScript配置文件通常来说,大多情况下,我们使用某种语法、插件等,它都会有自己一份默认的配置,在比较简单的项目中,毋需我们编写配置文件。但是,在利用TypeScript书写webpack配置文件时,我们还需要额外编写TypeScript配置文件tsconfig.json:{ “compilerOptions”: { /* ts-node只支持commonjs规范 */ “module”: “commonjs”, “target”: “es5”, “esModuleInterop”: true, }}这是因为我们在前面安装的依赖ts-node不支持commonjs之外的模块语法,所以我们必须在TypeScript的配置文件tsconfig.json配置compilerOptions中module字段为:commonjs,否则,webpack会报错。ps:在安装依赖的时候,我们也需要考虑依赖的局限性。比如某些依赖依赖于其他的依赖,在项目开发的时候,我们需要把其涉及到的其他依赖一同安装。另外,依赖不是万能的,在确定安装依赖的时候,需要额外去学习该依赖相关知识。coffeeScript1、安装依赖与上面的内容相似,首先我们需要安装相关依赖:npm install –save-dev coffee-script2、编写webpack配置文件(1)把webpack配置文件的文件名改为:webpack.config.coffeeCoffeeScript的文件拓展名为.coffee,所以我们需要同时把webpack配置文件的文件名改为.coffee拓展名(原来默认为webpack.config.js)当我们把webpack配置文件名拓展改为.coffee时,webpack也会自动读取该拓展名下的文件。即不需要这样设置:webpack –config webpack.config.coffeewebpack会自动帮我们读取webpack.config.coffee文件,不需要我们再去设置(2)利用coffeeScript重新编写webpack.config.coffee文件//webpack.config.coffeewebpack = require(‘webpack’)path = require(‘path’)config = mode: ‘production’ entry: ‘./src/index.js’ output: path: path.resolve(__dirname, ‘dist’) filename: ‘bundle.js’module.exports = config用coffeeScript编写webpack配置文件时,毋需向TypeScript一样编写ts配置文件,因为coffeeScript安装的依赖没有其他的兼容性问题出现。ES6利用es6写webpack配置文件的原理同上面一样,都是把其他类型的语言语法编译成原生js。把es6编译成原生js可以使用babel进行编译(也有其他选择)。1、安装依赖npm install –save-dev babel-core babel-loader babel-preset-es2015 babel-preset-stage-3其中,要使用babel编译器,我们需要安装的依赖有babel-core。babel-core包中囊括了babel的核心方法。2、编写webpack配置文件由于es6语法写的文件名拓展也是.js,那么webpack如何识别该js文件,并把它交予babel进行编译呢?(1)webpack.config.[loader].js把webpack配置文件的文件名改为webpack.config.babel.js,其中babel字段表示需要优先使用babel-loader对该webpack配置文件进行编译。同样地,我们可以把webpack.config.[loader].js中的[loader]替换成我们需要的loader名。这也是我们需要安装babel-loader的原因。(2)编写webpack.config.babel.js为了体现es6语法,我们把webpack配置文件改写成:import path from ‘path’// example of an imported pluginconst CustomPlugin = config => ({ …config, name: ‘custom-plugin’});?export default { entry: ‘./src/index.js’, output: { filename: ‘bundle.js’, path: path.resolve(__dirname, ‘dist’) },}其中,import、export、… 语法为es6语法3、编写babel配置文件.babelrcbabel实质是一个支持众多语法编译转化的编译器,为了保证babel的可拓展性,作者把babel设计成可以灵活配置,支持安装插件的模式。因此,我们需要自行配置babel,使之支持es6编译。{ “presets”: [ “es2015”, “stage-3”],}其中,我们需要安装babel-preset-es2015的包,使得babel支持es6编译。另外,使用…config语法需要安装babel-preset-stage-3包,否则会编译错误。总之,我们可以使用各种各样的语言语法来编写webpack配置文件,它们的原理都是使用对应的编译器编译成原生的js。所以我们在编程的时候,都需要安装编译器的相关依赖,并且在必要的时候,还需要对编译器进行配置。 ...

April 2, 2019 · 1 min · jiezi

史上最清晰易懂的babel配置解析

标题党了哈哈哈~~~原文地址相信很多人和笔者从前一样,babel的配置都是从网上复制黏贴或者使用现成的脚手架,虽然这能够工作但还是希望大家能够知其所以然,因此本文将对babel(babel@7)的配置做一次较为完整的梳理。语法和apies6增加的内容可以分为语法和api两部分,搞清楚这点很重要,新语法比如箭头函数、解构等:const fn = () => {}const arr2 = […arr1]新的api比如Map、Promise等:const m = new Map()const p = new Promise(() => {})@babel/core@babel/core,看名字就知道这是babel的核心,没他不行,所以首先安装这个包npm install @babel/core它的作用就是根据我们的配置文件转换代码,配置文件通常为.babelrc(静态文件)或者babel.config.js(可编程),这里以.babelrc为例,在项目的根目录下创建一个空文件命名为.babelrc,然后创建一个js文件(test.js)测试用:/* test.js /const fn = () => {}这里我们安装下@babel/cli以便能够在命令行使用babelnpm install @babel/cli安装完成后执行babel编译,命令行输入npx babel test.js –watch –out-file test-compiled.js结果发现test-compiled.js的内容依然是es6的箭头函数,不用着急,我们的.babelrc还没有写配置呢Plugins和PresetsNow, out of the box Babel doesn’t do anything. It basically acts like const babel = code => code; by parsing the code and then generating the same code back out again. You will need to add plugins for Babel to do anything.上面是babel官网的一段话,可以理解为babel是基于插件架构的,假如你什么插件也不提供,那么babel什么也不会做,即你输入什么输出的依然是什么。那么我们现在想要把剪头函数转换为es5函数只需要提供一个箭头函数插件就可以了:/ .babelrc /{ “plugins”: ["@babel/plugin-transform-arrow-functions"] }转换后的test-compiled.js为:/ test.js /const fn = () => {}/ test-compiled.js /const fn = function () {}那我想使用es6的解构语法怎么办?很简单,添加解构插件就行了:/ .babelrc /{ “plugins”: [ “@babel/plugin-transform-arrow-functions”, “@babel/plugin-transform-destructuring” ] }问题是有那么多的语法需要转换,一个个的添加插件也太麻烦了,幸好babel提供了presets,他可以理解为插件的集合,省去了我们一个个引入插件的麻烦,官方提供了很多presets,比如preset-env(处理es6+规范语法的插件集合)、preset-stage(处理尚处在提案语法的插件集合)、preset-react(处理react语法的插件集合)等,这里我们主要介绍下preset-env:/ .babelrc /{ “presets”: ["@babel/preset-env"] }preset-env@babel/preset-env is a smart preset that allows you to use the latest JavaScript without needing to micromanage which syntax transforms (and optionally, browser polyfills) are needed by your target environment(s).以上是babel官网对preset-env的介绍,大致意思是说preset-env可以让你使用es6的语法去写代码,并且只转换需要转换的代码。默认情况下preset-env什么都不需要配置,此时他转换所有es6+的代码,然而我们可以提供一个targets配置项指定运行环境:/ .babelrc /{ “presets”: [ ["@babel/preset-env", { “targets”: “ie >= 8” }] ] }此时只有ie8以上版本浏览器不支持的语法才会被转换,查看我们的test-compiled.js文件发现一切都很好:/ test.js /const fn = () => {}const arr1 = [1, 2, 3]const arr2 = […arr1]/ test-compiled.js /var fn = function fn() {};var arr1 = [1, 2, 3];var arr2 = [].concat(arr1);@babel/polyfill现在我们稍微改一下test.js:/ test.js /const fn = () => {}new Promise(() => {})/ test-compiled.js /var fn = function fn() {};new Promise(function () {});我们发现Promise并没有被转换,什么!ie8还支持Promise?那是不可能的…。还记得本文开头提到es6+规范增加的内容包括新的语法和新的api,新增的语法是可以用babel来transform的,但是新的api只能被polyfill,因此需要我们安装@babel/polyfill,再简单的修改下test.js如下:/ test.js /import ‘@babel/polyfill’const fn = () => {}new Promise(() => {})/ test-compiled.js /import ‘@babel/polyfill’;var fn = function fn() {};new Promise(function () {});现在代码可以完美的运行在ie8的环境了,但是还存在一个问题:@babel/polyfill这个包的体积太大了,我们只需要Promise就够了,假如能够按需polyfill就好了。真巧,preset-env刚好提供了这个功能:/ .babelrc /{ “presets”: [ ["@babel/preset-env", { “modules”: false, “useBuiltIns”: “entry”, “targets”: “ie >= 8” }] ] }我们只需给preset-env添加一个useBuiltIns配置项即可,值可以是entry和usage,假如是entry,会在入口处把所有ie8以上浏览器不支持api的polyfill引入进来,如下:/ test.js /import ‘@babel/polyfill’const fn = () => {}new Promise(() => {})/ test-compiled.js /import “core-js/modules/es6.array.copy-within”;import “core-js/modules/es6.array.every”;import “core-js/modules/es6.array.fill”;… //省略若干引入import “core-js/modules/web.immediate”;import “core-js/modules/web.dom.iterable”;import “regenerator-runtime/runtime”;var fn = function fn() {};new Promise(function () {});细心的你会发现transform后,import ‘@babel/polyfill’消失了,反倒是多了一堆import ‘core-js/…‘的内容,事实上,@babel/polyfill这个包本身是没有内容的,它依赖于core-js和regenerator-runtime这两个包,这两个包提供了es6+规范的运行时环境。因此当我们不需要按需polyfill时直接引入@babel-polyfill就行了,它会把core-js和regenerator-runtime全部导入,当我们需要按需polyfill时只需配置下useBuiltIns就行了,它会根据目标环境自动按需引入core-js和regenerator-runtime。前面还提到useBuiltIns的值还可以是usage,其功能更为强大,它会扫描你的代码,只有你的代码用到了哪个新的api,它才会引入相应的polyfill:/ .babelrc /{ “presets”: [ ["@babel/preset-env", { “modules”: false, “useBuiltIns”: “usage”, “targets”: “ie >= 8” }] ] }transform后的test-compiled.js相应的会简化很多:/ test.js /const fn = () => {}new Promise(() => {})/ test-compiled.js /import “core-js/modules/es6.promise”;import “core-js/modules/es6.object.to-string”;var fn = function fn() {};new Promise(function () {});遗憾的是这个功能还处于试验状态,谨慎使用。 事实上假如你是在写一个app的话,以上关于babel的配置差不多已经够了,你可能需要添加一些特定用途的Plugin和Preset,比如react项目你需要在presets添加@babel/preset-react,假如你想使用动态导入功能你需要在plugins添加@babel/plugin-syntax-dynamic-import等等,这些不在赘述。假如你是在写一个公共的库或者框架,下面提到的点可能还需要你注意下。@babel/runtime有时候语法的转换相对复杂,可能需要一些helper函数,如转换es6的class:/ test.js /class Test {}/ test-compiled.js /function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(“Cannot call a class as a function”); } }var Test = function Test() { _classCallCheck(this, Test);};示例中es6的class需要一个_classCallCheck辅助函数,试想假如我们多个文件中都用到了es6的class,那么每个文件都需要定义一遍_classCallCheck函数,这也是一笔不小的浪费,假如将这些helper函数抽离到一个包中,由所有的文件共同引用则可以减少可观的代码量。而@babel/runtime做的正好是这件事,它提供了各种各样的helper函数,但是我们如何知道该引入哪一个helper函数呢?总不能自己手动引入吧,事实上babel提供了一个@babel/plugin-transform-runtime插件帮我们自动引入helper。我们首先安装@babel/runtime和@babel/plugin-transform-runtime:npm install @babel/runtime @babel/plugin-transform-runtime然后修改babel配置如下:/ .babelrc /{ “presets”: [ ["@babel/preset-env", { “modules”: false, “useBuiltIns”: “usage”, “targets”: “ie >= 8” }] ], “plugins”: [ “@babel/plugin-transform-runtime” ] }现在我们再来看test-compiled.js文件,里面的_classCallCheck辅助函数已经是从@babel/runtime引入的了:/ test.js /class Test {}/ test-compiled.js /import _classCallCheck from “@babel/runtime/helpers/classCallCheck”;var Test = function Test() { _classCallCheck(this, Test);};看到这里你可能会说,这不扯淡嘛!几个helper函数能为我减少多少体积,我才懒得安装插件。事实上@babel/plugin-transform-runtime还有一个更重要的功能,它可以为你的代码创建一个sandboxed environment(沙箱环境),这在你编写一些类库等公共代码的时候尤其重要。上文我们提到,对于Promise、Map等这些es6+规范的api我们是通过提供polyfill兼容低版本浏览器的,这样做会有一个副作用就是污染了全局变量,假如你是在写一个app还好,但如果你是在写一个公共的类库可能会导致一些问题,你的类库可能会把一些全局的api覆盖掉。幸好@babel/plugin-transform-runtime给我们提供了一个配置项corejs,它可以将这些变量隔离在局部作用域中:/ .babelrc /{ “presets”: [ ["@babel/preset-env", { “modules”: false, “targets”: “ie >= 8” }] ], “plugins”: [ ["@babel/plugin-transform-runtime", { “corejs”: 2 }] ] }注意:这里一定要配置corejs,同时安装@babel/runtime-corejs2,不配置的情况下@babel/plugin-transform-runtime默认是不引入这些polyfill的helper的。corejs的值现阶段一般指定为2,可以近似理解为是@babel/runtime的版本。我们现在再来看下test-compiled.js被转换成了什么:/ test.js /class Test {}new Promise(() => {})/ test-compiled.js /import _Promise from “@babel/runtime-corejs2/core-js/promise”;import _classCallCheck from “@babel/runtime-corejs2/helpers/classCallCheck”;var Test = function Test() { _classCallCheck(this, Test);};new _Promise(function () {});如我们所愿,已经为Promise的polyfill创建了一个沙箱环境。最后我们再为test.js稍微添加点内容:/ test.js /class Test {}new Promise(() => {})const b = [1, 2, 3].includes(1)/ test-compiled.js */import _Promise from “@babel/runtime-corejs2/core-js/promise”;import _classCallCheck from “@babel/runtime-corejs2/helpers/classCallCheck”;var Test = function Test() { _classCallCheck(this, Test);};new _Promise(function () {});var b = [1, 2, 3].includes(1);可以发现,includes方法并没有引入辅助函数,可这明明也是es6里面的api啊。这是因为includes是数组的实例方法,要想polyfill必须修改Array的原型,这样一来就污染了全局环境,因此@babel/plugin-transform-runtime是处理不了这些es6+规范的实例方法的。tips以上基本是本文的全部内容了,最后再来个总结和需要注意的地方:本文没有提到preset-stage,事实上babel@7已经不推荐使用它了,假如你需要使用尚在提案的语法,请直接添加相应的plugin。对于普通项目,可以直接使用preset-env配置polyfill对于类库项目,推荐使用@babel/runtime,需要注意一些实例方法的使用本文内容是基于babel@7,项目中遇到问题可以尝试更新下babel-loader的版本…待补充全文完 ...

March 31, 2019 · 3 min · jiezi

如何写一个Babel插件

前言之前看到一位大佬的博客, 介绍了babel的原理, 以及如何写一个babel的插件, 抱着试试看的想法, 照葫芦画瓢的自己写了一个简单的babel插件, 该插件的作用就是将代码字符串中的表达式, 直接转换为对应的计算结果。例如: const code = const result = 1 + 1转化为const code = const result = 2。当然这一篇文章非常的浅显, 但是对了解Babel的原理以及AST的基本概念是足够的了。相关链接Babel插件文档可以随时查看AST抽象语法树插件的源码const t = require(‘babel-types’)const visitor = { // 二元表达式类型节点的访问者 BinaryExpression(path) { // 子节点 // 访问者会一层层遍历AST抽象语法树, 会树形遍历AST的BinaryExpression类型的节点 const childNode = path.node let result = null if ( t.isNumericLiteral(childNode.left) && t.isNumericLiteral(childNode.right) ) { const operator = childNode.operator switch (operator) { case ‘+’: result = childNode.left.value + childNode.right.value break case ‘-’: result = childNode.left.value - childNode.right.value break case ‘/’: result = childNode.left.value / childNode.right.value break case ‘’: result = childNode.left.value * childNode.right.value break } } if (result !== null) { // 替换本节点为数字类型 path.replaceWith( t.numericLiteral(result) ) if (path.parentPath) { const parentType = path.parentPath.type if (visitor[parentType]) { visitorparentType } } } }, // 属性表达式 MemberExpression(path) { const childNode = path.node let result = null if ( t.isIdentifier(childNode.object) && t.isIdentifier(childNode.property) && childNode.object.name === ‘Math’ ) { result = Math[childNode.property.name] } if (result !== null) { const parentType = path.parentPath.type if (parentType !== ‘CallExpression’) { // 替换本节点为数字类型 path.replaceWith( t.numericLiteral(result) ) if (visitor[parentType]) { visitorparentType } } } }, // 一元表达式 UnaryExpression (path) { const childNode = path.node let result = null if ( t.isLiteral(childNode.argument) ) { const operator = childNode.operator switch (operator) { case ‘+’: result = childNode.argument.value break case ‘-’: result = -childNode.argument.value break } } if (result !== null) { // 替换本节点为数字类型 path.replaceWith( t.numericLiteral(result) ) if (path.parentPath) { const parentType = path.parentPath.type if (visitor[parentType]) { visitorparentType } } } }, // 函数执行表达式 CallExpression(path) { const childNode = path.node // 结果 let result = null // 参数的集合 let args = [] // 获取函数的参数的集合 args = childNode.arguments.map(arg => { if (t.isUnaryExpression(arg)) { return arg.argument.value } }) if ( t.isMemberExpression(childNode.callee) ) { if ( t.isIdentifier(childNode.callee.object) && t.isIdentifier(childNode.callee.property) && childNode.callee.object.name === ‘Math’ ) { result = Math[childNode.callee.property.name].apply(null, args) } } if (result !== null) { // 替换本节点为数字类型 path.replaceWith( t.numericLiteral(result) ) if (path.parentPath) { const parentType = path.parentPath.type if (visitor[parentType]) { visitorparentType } } } }}module.exports = function () { return { visitor }}基本概念建议先阅读一下这一篇文档babel工作的原理Babel对代码进行转换,会将JS代码转换为AST抽象语法树(解析),对树进行静态分析(转换),然后再将语法树转换为JS代码(生成)。每一层树被称为节点。每一层节点都会有type属性,用来描述节点的类型。其他属性用来进一步描述节点的类型。// 将代码生成对应的抽象语法树// 代码const result = 1 + 1// 代码生成的AST{ “type”: “Program”, “start”: 0, “end”: 20, “body”: [ { “type”: “VariableDeclaration”, “start”: 0, “end”: 20, “declarations”: [ { “type”: “VariableDeclarator”, “start”: 6, “end”: 20, “id”: { “type”: “Identifier”, “start”: 6, “end”: 12, “name”: “result” }, “init”: { “type”: “BinaryExpression”, “start”: 15, “end”: 20, “left”: { “type”: “Literal”, “start”: 15, “end”: 16, “value”: 1, “raw”: “1” }, “operator”: “+”, “right”: { “type”: “Literal”, “start”: 19, “end”: 20, “value”: 1, “raw”: “1” } } } ], “kind”: “const” } ], “sourceType”: “module”}解析解析分为词法解析和语法分析, 词法解析将代码字符串生成令牌流, 而语法分析则会将令牌流转换成AST抽象语法树转换节点的路径(path)对象上, 会暴露很多添加, 删除, 修改AST的API, 通过操作这些API实现对AST的修改生成生成则是通过对修改后的AST的遍历, 生成新的源码遍历AST是树形的结构, AST的转换的步骤就是通过访问者对AST的遍历实现的。访问者会定义处理不同的节点类型的方法。遍历树形结构的同时,, 遇到对应的节点类型会执行相对应的方法。访问者Visitors访问者本身就是一个对象,对象上不同的属性, 对应着不同的AST节点类型。例如,AST拥有BinaryExpression(二元表达式)类型的节点, 如果在访问者上定义BinaryExpression属性名的方法, 则这个方法在遇到BinaryExpression类型的节点, 就会执行, BinaryExpression方法的参数则是该节点的路径。注意对每一个节点的遍历会执行两次, 进入节点一次, 退出节点一次const visitors = { enter (path) { // 进入该节点 }, exit (path) { // 退出该节点 }}路径每一个节点都拥有自身的路径对象(访问者的参数, 就是该节点的路径对象), 路径对象上定义了不同的属性和方法。例如: path.node代表了该节点的子节点, path.parent则代表了该节点的父节点。path.replaceWithMultiple方法则定义的是替换该节点的方法。访问者中的路径节点的路径信息, 存在于访问者的参数中, 访问者的默认的参数就是节点的路径对象第一个插件我们来写一个将const result = 1 + 1字符串解析为const result = 2的简单插件。我们首先观察这段代码的AST, 如下。我们可以看到BinaryExpression类型(二元表达式类型)的节点, 中定义了这段表达式的主体(1 + 1), 1 分别是BinaryExpression节点的子节点left,BinaryExpression节点的子节点right,而加号则是BinaryExpression节点的operator的子节点// 经过简化之后{ “type”: “Program”, “body”: [ { “type”: “VariableDeclaration”, “declarations”: [ { “type”: “VariableDeclarator”, “id”: { “type”: “Identifier”, “name”: “result” }, “init”: { “type”: “BinaryExpression”, “left”: { “type”: “Literal”, “value”: 1 }, “operator”: “+”, “right”: { “type”: “Literal”, “value”: 1 } } } ] } ]}接下来我们来处理这个类型的节点,代码如下const t = require(‘babel-types’)const visitor = { BinaryExpression(path) { // BinaryExpression节点的子节点 const childNode = path.node let result = null if ( // isNumericLiteral是babel-types上定义的方法, 用来判断节点的类型 t.isNumericLiteral(childNode.left) && t.isNumericLiteral(childNode.right) ) { const operator = childNode.operator // 根据不同的操作符, 将left.value, right.value处理为不同的结果 switch (operator) { case ‘+’: result = childNode.left.value + childNode.right.value break case ‘-’: result = childNode.left.value - childNode.right.value break case ‘/’: result = childNode.left.value / childNode.right.value break case ‘’: result = childNode.left.value * childNode.right.value break } } if (result !== null) { // 计算出结果后 // 将本身的节点,替换为数字类型的节点 path.replaceWith( t.numericLiteral(result) ) } }}我们定义一个访问者, 在上面定义BinaryExpression的属性的方法。运行结果如我们预期, const result = 1 + 1被处理为了const result = 2。但是我们将代码修改为const result = 1 + 2 + 3发现结果变为了 const result = 3 + 3, 这是为什么呢?我们来看一下1 + 2 + 3的AST抽象语法树.// 经过简化的ASTtype: ‘BinaryExpression’ - left - left - left type: ‘Literal’ value: 1 - opeartor: ‘+’ - right type: ‘Literal’ value: 2 - opeartor: ‘+’ - right type: ‘Literal’ value: 3我们上面的代码的判断条件是。t.isNumericLiteral(childNode.left) && t.isNumericLiteral(childNode.right), 在这里只有最里层的AST是满足条件的。因为整个AST结构类似于, (1 + 2) + 3 => (left + rigth) + right。解决办法是,将内部的 1 + 2的节点替换成数字节点3之后,将数字节点3的父路径(parentPath)重新执行BinaryExpression的方法(数字类型的3节点和right节点), 通过递归的方式,替换所有的节点。修改后的代码如下。BinaryExpression(path) { const childNode = path.node let result = null if ( t.isNumericLiteral(childNode.left) && t.isNumericLiteral(childNode.right) ) { const operator = childNode.operator switch (operator) { case ‘+’: result = childNode.left.value + childNode.right.value break case ‘-’: result = childNode.left.value - childNode.right.value break case ‘/’: result = childNode.left.value / childNode.right.value break case ‘*’: result = childNode.left.value * childNode.right.value break } } if (result !== null) { // 替换本节点为数字类型 path.replaceWith( t.numericLiteral(result) ) BinaryExpression(path.parentPath) }}结果如我们预期, const result = 1 + 2 + 3 可以被正常的解析。但是这个插件还不具备对Math.abs(), Math.PI, 有符号的数字的处理,我们还需要在访问者上定义更多的属性。最后, 对于Math.abs函数的处理可以参考上面的源码. ...

March 18, 2019 · 4 min · jiezi

使用 Webpack 与 Babel 配置 ES6 开发环境

使用 Webpack 与 Babel 配置 ES6 开发环境安装 Webpack安装:# 本地安装$ npm install –save-dev webpack webpack-cli# 全局安装$ npm install -g webpack webpack-cli在项目根目录下新建一个配置文件—— webpack.config.js 文件:const path = require(‘path’);module.exports = { mode: ’none’, entry: ‘./src/index.js’, output: { filename: ‘bundle.js’, path: path.resolve(__dirname, ‘dist’) }}在 src 目录下新建 a.js 文件:export const isNull = val => val === nullexport const unique = arr => […new Set(arr)]在 src 目录下新建 index.js 文件:import { isNull, unique } from ‘./a.js’const arr = [1, 1, 2, 3]console.log(unique(arr))console.log(isNull(arr))执行编译打包命令,完成后打开 bundle.js 文件发现 isNull 和 unique 两个函数没有被编译,和 webpack 官方说法一致:webpack 默认支持 ES6 模块语法,要编译 ES6 代码依然需要 babel 编译器。安装配置 Babel 编译器使用 Babel 必须先安装 @babel/core 和 @babel/preset-env 两个模块,其中 @babel/core 是 Babel 的核心存在,Babel 的核心 api 都在这个模块里面,比如:transform。而 @babel/preset-env 是一个智能预设,允许您使用最新的 JavaScript,而无需微观管理您的目标环境需要哪些语法转换(以及可选的浏览器polyfill)。因为这里使用的打包工具是 Webpack,所以还需要安装 babel-loader 插件。安装:$ npm install –save-dev @babel/core @babel/preset-env babel-loader新建 .babelrc 文件:{ “presets”: [ “@babel/preset-env” ]}修改 webpack 配置文件(webpack.config.js):const path = require(‘path’);module.exports = { mode: ’none’, entry: ‘./src/index.js’, output: { filename: ‘bundle.js’, path: path.resolve(__dirname, ‘dist’) }, module: { rules: [ { test: /.js$/, loader: ‘babel-loader’, exclude: /node_modules/ } ] }}由于 babel 默认只转换 ES6 新语法,不转换新的 API,如:Set、Map、Promise等,所以需要安装 @babel/polyfill 转换新 API。安装 @babel/plugin-transform-runtime 优化代码,@babel/plugin-transform-runtime 是一个可以重复使用 Babel 注入的帮助程序代码来节省代码的插件。安装 @babel/polyfill、@babel/plugin-transform-runtime 两个插件:$ npm install –save-dev @babel/polyfill @babel/plugin-transform-runtime修改 .babelrc 配置文件:{ “presets”: [ ["@babel/preset-env", { “useBuiltIns”: “usage”, // 在每个文件中使用polyfill时,为polyfill添加特定导入。利用捆绑器只加载一次相同的polyfill。 “modules”: false // 启用将ES6模块语法转换为其他模块类型,设置为false不会转换模块。 }] ], “plugins”: [ ["@babel/plugin-transform-runtime", { “helpers”: false }] ]}最后,配置兼容的浏览器环境。在 .babelrc 配置文件中设置 targets 属性:{ “presets”: [ ["@babel/preset-env", { “useBuiltIns”: “usage”, “modules”: false, “targets”: { “browsers”: “last 2 versions, not ie <= 9” } }] ], “plugins”: [ ["@babel/plugin-transform-runtime", { “helpers”: false }] ]}执行命令编译代码,完成后检查 bundle.js 文件,是否成功转换新 API 。如果发现以下代码即说明转换成功:// 23.2 Set Objectsmodule.exports = webpack_require(80)(SET, function (get) { return function Set() { return get(this, arguments.length > 0 ? arguments[0] : undefined); };}, { // 23.2.3.1 Set.prototype.add(value) add: function add(value) { return strong.def(validate(this, SET), value = value === 0 ? 0 : value, value); }}, strong);其他关于 js 压缩和 Webpack 启用 tree shaking 功能的设置本文不在赘述。配置文件详情概览package.json 文件:{ “name”: “demo”, “version”: “1.0.0”, “description”: “”, “main”: “index.js”, “scripts”: { “test”: “echo "Error: no test specified" && exit 1”, “dev”: “webpack” }, “keywords”: [], “author”: “”, “license”: “ISC”, “devDependencies”: { “@babel/core”: “^7.3.4”, “@babel/plugin-transform-runtime”: “^7.3.4”, “@babel/polyfill”: “^7.2.5”, “@babel/preset-env”: “^7.3.4”, “babel-loader”: “^8.0.5”, “webpack”: “^4.29.6”, “webpack-cli”: “^3.2.3” }}webpack.config.js 文件:const path = require(‘path’);module.exports = { mode: ’none’, entry: ‘./src/index.js’, output: { filename: ‘bundle.js’, path: path.resolve(__dirname, ‘dist’) }, module: { rules: [ { test: /.js$/, loader: ‘babel-loader’, exclude: /node_modules/ } ] }}.babelrc 文件:{ “presets”: [ ["@babel/preset-env", { “useBuiltIns”: “usage”, “modules”: false, “targets”: { “browsers”: “last 2 versions, not ie <= 9” } }] ], “plugins”: [ ["@babel/plugin-transform-runtime", { “helpers”: false }] ]}符录usuallyjs 项目是本人最近建设的开源项目,欢迎感兴趣的同行交流。usuallyjs: https://github.com/JofunLiang/usuallyjs ...

March 11, 2019 · 2 min · jiezi

从24M到1M: 一个react+antd后台系统构建打包历程

虽然在工作中用react+antd写页面写了一年,但从来没自己去认认真真配置一个webpack,去分析去优化自己打出的包。在工程化成熟或者大点的公司,都有自己的打包工具,所以自己工作中很少去琢磨这些。为了试一下写出的组件库(antd-doddle)性能,就尝试自己去写一个webpack构建,真的是吓了自己一跳,流水账(tu)开始。从npm run dev开始打包工具:webpack4 + babel6 开始前,大概说一下项目内容。项目基于react+react-router+antd+antd-doddle,自己日常在这个项目做一些技术验证与demo,就4个页面。组成如下: <Content style={{ margin: ‘24px 16px’, padding: 24, background: ‘#fff’, minHeight: 280 }}> <Switch> <Route exact path={menus.home.path} component={Home} /> <Route exact path={menus.renderProps.path} component={ExampleTable} /> <Route exact path={menus.hoc.path} component={Hoc} /> <Route exact path={menus.learnTest.path} component={LearnTest} /> </Switch> </Content>页面js与公共(node_modules引用)js使用splitChunks分开打包。npm run dev,结果如下图,async.bundle.js大小24M,有点惊人的大。打包工具:webpack4 + babel7 开始正题,npm run builddevelopment与production由于webpack在(mode)模式development与production还是有很大区别,当我直接将mode从development变成production,再运行npm start。打包大小还是有明显的变化,同时也报了三个警告,包体积大于244kb打包大小从24M缩减到17M,由于run start是启用的webpack-dev-server来构建的,所以对应的production模式默认启用UglifyJsPlugin是无效的,但是可以手动去加入这段配置,然后打包大小能看到直线下降到3.25M,见下图 optimization: { minimizer: [ new UglifyJsPlugin() ], splitChunks: { name: ‘async’, minSize: 30000, cacheGroups: { vendors: { test: /[\/]node_modules[\/]/, chunks: ‘all’ }, } } }production模式构建以上其实都是在用webpack-dev-server构建,当用webpack去构建时,然后npm run build,结果是这样的: 压缩后大小已经减小到2.6M,还是有实质的进步,然后通过BundleAnalyzerPlugin这个插件看包的构成,会发现antd,antd-doddle,moment占了绝大多数的空间。 恰好我知道moment虽然不支持按需打包,由于支持了多语言,所以省去无用的语言选项,就可以有明显的优化,所以使用ContextReplacementPlugin这个插件,并加上只匹配中文的筛选,如下所示: new webpack.ContextReplacementPlugin( /moment[/\]locale$/, /zh-cn/, )打包体积由2.6减少到2.3M。当一个一个浏览完包的构成时,大小组成是这样的:atnd(609kb);antd-icon(477kb);antd-doddle(391kb)dragt.js(160kb)immutable(55*2kb)moment(53 *2kb)react-dom(100kb)others(400kb)我留下了一些疑问:发现有两个moment.js,两个immutable.js,一个引用路径是正常的,一个是包里的相对路径。然后还有一个draft.js(引用来源是antd),一个富文本编辑库,但我并没有这相关的组件应用,所以被打进来也是不讲道理的。然后看起来antd是按需引入(es module),但是我项目就4个页面,组件最多也就用到他所拥有的1/3,但是上面显示的285个,我的antd-doddle就更吓人了,居然有138个,我库中总共也就30来个导出,所以也是诡异。为了减少大小,我还使用了babel-plugin-import插件,但没有实质性的改变,然后看到包的路径是es,说明现有的构建本来就已经做了tree-shaking,所以这确实是无用功。但公司项目和网上大多数文章提到的,都是打包都能优化到1M左右,所以应该还有优化的空间。后面我干了什么呢?升级才是硬道理打包工具:webpack4 + babel7 babel6升到babel7,修改一下配置。然后再运行npm run build, 结果如下:当一个一个浏览完包的构成时,大小组成是这样的:atnd(367kb);antd-icon(475kb);react-dom(102kb)antd-doddle(66kb)immutable(55kb)moment(53kb)others(218kb)不能说是奇迹,但确实解决了一些上面留下的疑问:moment,immutable重复打包;draft无端引入;antd与antd-doddle包数激增。大小也减少了至少1M,所以升级才是自然界应有的规律,更不要说前端。 babel6到babel7,到底发生了什么看到这种优化的效果真的让我惊喜与惊讶,但究竟是什么带来了这种变化更让我更好奇。还没抓到核心的区别,有清楚的,请指点一下,怎么避免js公共函数的重复注入,怎么来优化编译来完美对接后面webpack打包的?想往外去看看大神们都什么意见,可特殊时期,我的梯子被折断了(衰)。文章提到的项目地址:webpack打包,master分支是对应babel6,roadhg分支是对应babel7 ...

March 7, 2019 · 1 min · jiezi

vue2.5.2+webpack3.6.0环境下es6语法解释配置

前置准备下载对应的loader第一步配置webpack配置路径:webpack.base.js->module->rules->属性解析:test 使用正则语法匹配js文件loader js文件加载器include 需要处理的路径exclude 不需要处理的路径第二步配置babel配置路径:根目录下建立.labelrc属性解析:“presets”:预设前置,里面包含的每个字段都要下载对应的npm包,比如 “env” -> npm install babel-preset-env “stage-2” -> npm install babel-preset-stage-2结尾根据上面步骤大概能解决问题

March 6, 2019 · 1 min · jiezi

基于 Babel 的 npm 包最小化设置

翻译:疯狂的技术宅原文:http://2ality.com/2017/07/npm…本文首发微信公众号:jingchengyideng欢迎关注,每天都给你推送新鲜的前端技术文章本文描述了通过 Babel 生成 npm 包的最小设置。你可以在 GitHub 中看到案例 re-template-tag 的设置。GitHub:https://github.com/rauschma/r…1 核心结构有两组文件:目录 esm/ 里有库的(还转换)实际源码。package.json 中的 module 属性指向 esm/index.js目录 test/ 含有基于AVA的对 esm/ 中代码的测试。目录 cjs/ 有 ESM 文件的已转换版本,它们是 CommonJS 格式并支持在当前版本的 Node.js 环境下运行。package.json 中的 main 属性指向 cjs/index.js此结构支持两种用例:Node.js 应用使用 cjs/ 中的文件。Web应用(通过 webpack 等)使用 esm/ 中的文件。它们通过 babel-preset-env 将这些文件转换为其目标平台支持的功能集。 另一篇文章 中描述了如何执行这个操作。到此我们仅部分解决了如何填充可能缺少的库这个问题。接下来要彻底解决它2 .gitignorecjsnode_modulescjs/ 没有提交到 git。我们只是在 npm 发布包之前按需生成它。3 .npmignorenode_modules发布到 npm 时,我们需要包含 cjs/。这就是除了 .gitignore 之外我们还需要 .npmignore 的原因。4 package.jsonpackage.json 的主要部分可以使用以下脚本:“scripts”: { “build”: “babel esm –out-dir cjs”, “prepublishOnly”: “npm run build”, “test”: “ava”},build 用于生成 cjs/中的文件。prepublishOnly 能够确保在我们发布到 npm 之前始终构建 cjs /。test 通过 AVA 运行测试。因此,我们有以下依赖项(仅在开发时):“devDependencies”: { “babel-cli”: “^6.24.1”, “ava”: “^0.21.0”, “babel-preset-env”: “^1.5.1”, “babel-register”: “^6.24.1”},单元测试需要ava。babel-cli 提供命令 babel。babel-register 让 AVA 通过 Babel 执行测试。babel-preset-env 是 Babel 用于转换的预设。“main”: “./cjs/index.js”,“module”: “./esm/index.js”,main 是 CommonJS 格式的包入口点(包括在Node.js上运行的普通模块)。module是 ESM 格式的包入口点(包括webpack,取决于你如何设置它)。有关这两个属性的更多信息:“设置多平台 npm 包”。配置 Babel对于Babel,我们用 babel-preset-env 典型的方式为当前运行的 Node.js 生成代码。“babel”: { “presets”: [ [ “env”, { “targets”: { “node”: “current” } } ] ]},配置 AVA对于 AVA,我们需要 babel-register,它通过 Babel 转换所有测试和导入。 “babel”: “inherit” 表示 AVA 使用上一节中的配置。“ava”: { “require”: [ “babel-register” ], “babel”: “inherit”}5 结论以上是通过 Babel 创建 npm 包最小库的方法。重要的一点是它允许包的客户端使用 babel-preset-env(就像通过npm提供未转换的源代码 ”中所讲的那样)。所以它并没有 100% 的正确使用 module ,但是有广泛支持的优势,并且没有引入另一个 package.json 属性。6 扩展阅读免费在线书籍: “Setting up ES6”Delivering untranspiled source code via npmbabel-preset-env: a preset that configures Babel for you欢迎继续阅读本专栏其它高赞文章:12个令人惊叹的CSS实验项目世界顶级公司的前端面试都问些什么CSS Flexbox 可视化手册过节很无聊?还是用 JavaScript 写一个脑力小游戏吧!从设计者的角度看 ReactCSS粘性定位是怎样工作的一步步教你用HTML5 SVG实现动画效果程序员30岁前月薪达不到30K,该何去何从第三方CSS安全吗?谈谈super(props) 的重要性本文首发微信公众号:jingchengyideng欢迎扫描二维码关注公众号,每天都给你推送新鲜的前端技术文章 ...

March 5, 2019 · 1 min · jiezi

一文读懂 babel7 的配置文件加载逻辑

近期,在波洞星球的PC官网项目中,我们采用了新版的 babel7 作为 ES 语法转换器。而 babel7 中的一大变更就是对配置文件的加载逻辑进行了改进,然而实际上对于不熟悉 babel 配置逻辑的朋友往往会带来更多问题。本文就是 babel7 配置文件的中文指南,它是英语渣渣的救星,是给懒人送到口边的一道美味。如有错误 概不负责 欢迎指正。前言babel7 从 2018年3月开始进入 alpha 阶段,时隔5个月直到 2018年8月份 release 第一个版本,目前的最新版是2019年2月26号发布的 7.3.4. 时光如梭,在这美好的 9012 年,ES2019 都快要发布了的时刻,我想: 是时候用一用 babel7 了。本文不是 babel7 的升级教程,而是对 babel7 的新变化和配置逻辑的一点心得。babel7 对monorepo 结构项目的优化恰好符合我们目前项目架构的预期,这简化了我们配置的复杂度,但其难以理解的配置加载逻辑,却让我踩了不少坑,这也正是本文的来源。说点变化在开始讲 babel7 的配置逻辑之前,我们先从以下几个方面来啰嗦几句 babel7 所做的变更及其逻辑意义。proposal 语法特性在历史上(babel6)的时代,人们通常使用 babel 提供的 preset-stage 预设来体验 ES6 之后的处于建议阶段的语法特性。例如做如下的 babel 配置:“presets”: [“es2015”, “react”, “stage-0”]其中,es2015 预设会包含 ES6 标准中所有语法特性;stage-0预设会包含当前(安装该预设npm包的时刻) 的 ES 语法进展中的 stage 0到3的特性(数字小的包含数字大的)。但事实上 babel 官方这样提供 stage 预设,会有不少问题例如:随着 es 标准的不断发展,大量的新特性几乎已经成为标准。与此同时,stage0-3阶段的特性必然也发生变化。可以说,stage0-3的阶段特性他们是不稳定的,极有可能在某个时机被TC39委员会除名、变更阶段、改变语法。尽管 babel-preset-* 预设会跟随TC39 保持一致的更新, 但这样的用法需要使用者也不断保持更新 才能跟标准一致历史上的 preset-es2015 配合 preset-stage-0 的做法极易产生疑惑,例如没有人知道他所需要的特性在stage几一个语言特性如果从 stage3 变更为 stage4,往往会导致以前的 stage0(包含了1、2、3) 的配置出问题。因为特性推进后,新的stage0中就不再包含该特性内容,但使用者可能不知道要把该特性所在的 ES标准 加入到配置中大量的社区工具 eslint 等等都依赖 babel;babel 的 preset-stage 预设更新就会导致这些社区工具频频出现问题。如今,babel 官方认为,把不稳定的 stage0-3 作为一种预设是不太合理的,因此废弃了 stage 预设,转而让用户自己选择使用哪个 proposal 特性的插件,这将带来更多的明确性(用户无须理解 stage,自己选的插件,自己便能明确的知道代码中可以使用哪个特性)。所有建议特性的插件,都改变了命名规范,即类似 @babel/plugin-proposal-function-bind 这样的命名方式来表明这是个 proposal 阶段特性。ES 标准特性对于正经的 ES 标准特性,babel从6开始就建议使用 babel-preset-env 这个能根据环境进行自动配置的预设。到了 babel7,我们就可以完全告别这几个历史预设了: preset-es2015/es2016/es2017/latest为什么 preset-env 要更好呢?我认为,对于开发者而言,关注目标用户平台(兼容哪些浏览器)要比关注 “编译为哪份ES标准” 要更易理解。把选择编译插件的事情交给 preset-env 就好了。它会根据 compat table 和你设置的目标用户平台选择正确的插件。polyfill跟 stage 预设的结局一样,对于处于建议阶段的特性,polyfill里面也移除了对他们的支持。以前的 babel-polyfill 是这么实现的:import “core-js/shim”; // included < Stage 4 proposals import “regenerator-runtime/runtime"现在的 @babel/polyfill 就直接引入 core-js v2 的属于ES正式标准的模块。这意味着,如果你需要使用处于 proposal 阶段的语法特性,你需要手工 import core-js 中的对应模块。命名空间从 babel7 开始,所有的官方插件和主要模块,都放在了 @babel 的命名空间下。从而可以避免在 npm 仓库中 babel 相关名称被抢注的问题。有必要说一下的,比如 @babel/node @babel/core @babel/clil @babel/preset-envtransform-runtime以前的 babel-transform-runtime 是包含了 helpers 和 polyfill。而现在的 @babel/runtime 只包含 helper,如果需要 polyfill,则需主动安装 core-js 的 runtime版本 @babel/runtime-corejs2 。并在 @babel/plugin-transform-runtime 的插件中做配置。说重点: 配置这是本文的重点,先来看一段 babel7 对配置的变更说明Babel has had issues previously with handling node_modules, symlinks, and monorepos. We’ve made some changes to account for this: Babel will stop lookup at the package.json boundary instead of looking up the chain. For monorepo’s we have added a new babel.config.js file that centralizes our config across all the packages (alternatively you could make a config per package). In 7.1, we’ve introduced a rootMode option for further lookup if necessary.段落的意思大概有这么几点:Babel将停止在package.json边界查找而不是查找链。译者注:这说明以前babel会递归向上查找babelrc 而现在检索行为会停在package.json所在层级。这可以解决部分符号链接的js向上查找babelrc错乱的问题。添加了一个新的项目全局babel.config.js文件,可以将整个项目的配置集中在所有包中。译者注:除了新增的这个全局配置,也可以同时支持以前的基于文件的.babelrc的配置引入了一个rootMode选项,以便在必要时按一定策略查找 babel.config.js除此之外,babel7 还有一个特性是:默认情况下,不会加载monorepo项目的任何独立子项目中的 .babelrc 文件然而,对上面的解释,你可能: 每个字都认识,连在一起却不知道在说什么。下面我们来剖析一下概念为了理解 babel7 的配置逻辑,我们就以 babel7 真正所解决的痛点 [monorepo 类型的项目] 为例来剖析。在此之前,我们需要预先确定几个概念。monorepo。这是个自造词。按我的理解,它的含义是说 单个大项目但是包含多个子项目 的含义。如果还是不能理解的话,就把 项目 二字 换成 npm模块包 (以package.json文件作为分界线)。即 单个npm包中又包含多个子npm包 的项目。 例如,波洞的 PC 版采用的是 Node.js 作为前端接入层的方式,在我们的项目结构组织上,是这样的:|- backend |-package.json|- frontend |-package.json|- node_modules|- config.js|- babel.config.js|- package.json这就是典型的 monorepo 结构。全局配置。在 babel 文档中又叫 项目级别的配置,特指 babel.config.js。如上图的monorepo结构,其 babel.config.js 就是全局配置/项目配置,该 babel 配置对 backend、frontend、甚至 node_modules 中的模块全部生效。局部配置。在 babel 文档中可能叫 相对于文件的配置。这种配置就是特指的 .babelrc 或 .babelrc.js 了。他们的生效范围是与待编译文件的位置有关的。规则懂了几种配置文件的概念和作用范围之后,我们就可以来根据文档和代码测试结果来精确描述 babel7 的配置规则。这里我们直接以 monorepo 类型项目为例来说,因为普通项目会更简单。下文中可能用到的名词解释:我们用 package 来代指一个具有独立 package.json 的项目,如上面案例中的 frontend 可以称作一个 package,backend也可以称作一个package; 我们用 相对配置 这个名词来表达所谓的 .babelrc 和 .babelrc.js,用全局配置来代指 babel.config.js这份配置对monorepo类型项目,babel7 的处理逻辑是:【全局配置】全局配置 babel.config.js 里的配置默认对整个项目生效,包括node_modules。除非通过 exclude 配置进行剔除。【全局配置】全局配置中如果没有配置 babelrcRoots 字段,那么babel 默认情况下不会加载任何子package中的相对配置(如.babelrc文件)。除非在全局配置中通过 babelrcRoots 字段进行配置。【全局配置】babel 全局配置文件所在的位置就决定了你的项目根目录在哪里,默认就是执行babel的当前工作目录,例如上面的例子,你在根目录执行babel,babel才能找到babel.config.js,从而确定该monorepo的根目录,进而将配置对整个项目生效【相对配置】相对配置可被加载的前提是在 babel.config.js 中配置了 babelrcRoots. 如 babelrcRoots: [’.’, ‘./frontend’],这表示要对当前根目录和frontend这个子package开启 .babelrc 的加载。(注意: 项目根目录除了可以拥有一个 babel.config.js,同时也可以拥有一个 .babelrc 相对配置)【相对配置】相对配置加载的边界是当前package的最顶层。假设上文案例中要编译 frontend/src/index.js 那么,该文件编译时可以加载 frontend 下的 .babelrc 配置,但无法向上检索总项目根目录下的 .babelrc实战还是以上面的代码结构为例。|- backend |-package.json|- frontend |-package.json|- node_modules|- config.js|- babel.config.js|- package.json该案例中,我们思考发现,我们需要利用 babel7 的全局配置能力。原因在于,monrepo 中存在多个 子 package。由于 babel7 默认检索 babelrc 的边界是 当前package。因此每个package中撰写的babelrc只会对当前package生效,这会导致我们的frontend中依赖根目录的config.js时无法得到正确的编译;另一个问题是: frontend和backend中的相同的babel配置部分无法共享 存在一定冗余。为此,我们需要在项目根目录设置一个 babel.config.js的配置,用它再配合babelrc来做babel配置的共享和融合。但是,问题很快来了:当工作目录不在根目录时,无法加载到全局配置。我们的前端编译脚本通常放置在 frontend目录下,(我们执行编译的工作目录是在 frontend 中),此时 babel build 行为的 工作目录 便是 frontend. 由于 babel 默认只在当前目录寻找 babel.config.js 这个全局配置,因此会导致无法找到根目录的 babel.config.js,这样我们所设想的整个项目的全局配置就无法生效。 幸好,babel7 提供了 rootMode 选项,可以将它指定为 “upward”, 这样babel 会自动向上寻找全局配置,并确定项目的根目录位置。设置方法:CLI: babel –rootMode=upwardwebpack: 在 babel-loader 的配置上设置 rootMode: ‘upward’现在,全局配置有了,我们可以在里面配置 babel 转译规则,它可以对全项目生效,frontend下的 vue.js 编译自然没有问题了。不过,假设我们 backend 项目中也要使用 babel 转译(目前我们实际在 backend 中并没有使用,因为我们认为只图esmodule而多加一层编译得不偿失),那么必然 backend 与 frontend 中的编译配置是不同的,frontend 需要加载 vue 的 jsx 插件和polyfill (useBuiltIns: usage,modules: false),而backend只需要转译基本模块语法(modules: true, useBuiltIns: false)。该场景的解决方案便是,为每个子 package 提供独立的 .babelrc 相对配置,在全局 babel.config.js 中设置共用的配置。此时项目组织结构如下:|- backend |- .babelrc.js |-package.json|- frontend |- .babelrc.js |-package.json|- node_modules|- config.js|- .babelrc.js // 这份配置在本场景下不需要(如果根目录下的代码有区别于子package的babel配置,则需要使用)|- babel.config.js|- package.json根目录的 babel.conig.js 配置应该如下:const presets = [ // 根、frontend、backend 的公共预设]const plugins = [ // 根、frontend、backend 的公共插件]module.exports = { presets, plugins, babelrcRoots: [’.’, ‘./frontend’, ‘./backend’] // 允许这两个子 package 加载 babelrc 相对配置}以为此时已经高枕无忧了?navie,由于我们前端 Vue.js 采用 webpack 打包。实际开发过程中发现,这种配置会造成 webpack 打包模块时出现故障,故障原因在于:同一个模块中错误混用 esmodule 和 commonjs 语法会造成 webpack故障。 前文讲到 全局配置 global.config.js 会作用到 整个项目,甚至包括 node_modules。因此babel编译时会同时编译 node_modules 下的模块,虽然模块作者不可能在一个js文件中混用不同模块语法,但他们作为释出包 通常是commonjs的模块语法。 而preset-env预设在编译时会通过 usage 方式 默认注入import语法的 polyfillSince Babel defaults to treating files are ES modules, generally these plugins/presets will insert import statements这便是蛋疼的来源:babel加载过的node_modules模块会变成 同一个js文件里既有commonjs语法又有esmodule语法。解决方案:不要对 node_modules 下的模块采用babel编译。我们需要在 babel.config.js 配置中增加选项:exclude: /node_modules/总结至此,我们的 monorepo 项目就可以使用一份 全局配置+两份相对配置,实现分别对 前端和后端 进行合理的ES6+语法的编译了。这是我们配置工程师的一小步,但是前端走向未来语法的一大步。总结 babel7 的配置加载逻辑如下:babel.config.js 是对整个项目(父子package) 都生效的配置,但要注意babel的执行工作目录。.babelrc 是对 待编译文件 生效的配置,子package若想加载.babelrc是需要babel配置babelrcRoots才可以(父package自身的babelrc是默认可用的)。任何package中的babelrc寻找策略是: 只会向上寻找到本包的 package.json 那一级。node_modules下面的模块一般都是编译好的,请剔除掉对他们的编译。如有需要,可以把个例加到 babelrcRoots 中。虽然写的很乱,但您有收获吗,有的话点个赞吧.或许你还没有看明白。没关系,知道最终的配置代码怎么粘贴就好了~ ...

March 1, 2019 · 3 min · jiezi

工程优化暨babel升级小记

小记背景随着业务代码的增多,项目代码的编译时长也在增多,遂针对这个痛点在dev下做些优化第一部分:优化dev编译时间这里优化的主要思路是在dev环境下,单独出来一个dll配置文件,将项目中的部分依赖包写入配置文件,最终生成一个在dev环境下专用的dll文件,这样处理的目的是减少开发时的编译时间(ps:经测试可以提升50%左右的编译效率),具体修改如下:独立dev的dll配置拷贝一份当前的dll.config.js文件,并重命名为开发环境专用dll-dev.config.js,并进行如下修改:// dll-dev.config.jsmodule.exports = { entry: { vendor: [ ‘moment’, ’nprogress’, ‘cookie_js’, ’echarts’, ‘jsbarcode’, ’lodash’, ’lodash-decorators’, ‘isomorphic-fetch’, ‘antd’, ‘react’, ‘react-dom’, ‘react-router’, ‘react-router-redux’, ‘redux’, ‘redux-fetch-elegant’, ‘redux-logger’, ‘redux-thunk’ … ] }}修改开发环境配置文件webpack.dev.config.js增加一条package.json命令,用于单独生成dev环境下的dll文件"scripts": { “dll-dev”: “./node_modules/.bin/webpack –config dll-dev.config.js”,}dev环境下执行这条新的命令行生成dll文件以及对应的json映射文件,以便省去dev下一些import进来的包文件编译,从而减少工程的整体编译时长npm run dll-dev第二部分:工程升级到babel@7+升级package依赖包 & 去除冗余and这里删除了’babel-preset-stage-2’,因为为了避免概念模糊不清以及防止出现由于提案的删除或变更而导致不可预见问题,故而babel@7+中删除了阶段预设。其他依赖包从v@6+升级到v@7+,并采用babel@7+中的最新官方包名称。用于antd按需加载的babel-plugin-import也需要跟着babel进行升级到1.11.0,因为配置语法和资源的目录名称都改变了(详见babel.config.js)。修改babel配置文件在babel@7+中,增加了一个新的配置文件babel.config.js,这样可以让配置文件变得更加灵活,更适合babel所采用的monorepo管理,比如可以将配置集中在所有包中、也可以为每一个包单独创建配置,我们这里采用这个配置文件,因为需要在config里写一些判断逻辑,以实现dev和pro的更深层次隔离ps:详细的配置官方说明详见6.x vs 7.x新的babel配置文件如下:// babel.config.jsmodule.exports = function (api) { api.cache(true) const presets = [ ‘@babel/preset-env’, ‘@babel/react’ ] const plugins = [ [’@babel/plugin-transform-runtime’, { ‘helpers’: false, ‘regenerator’: true }], [’@babel/plugin-proposal-class-properties’, { ’loose’: true }], [‘import’, { ’libraryName’: ‘antd’, ’libraryDirectory’: ’lib’, ‘style’: ‘css’ }, ‘ant’] ] return { presets, plugins }}编译测试对比使用同一台电脑及开发环境进行测试比较升级前执行编译耗时如下:升级后执行编译耗时如下:升级前进行修改保存耗时如下:升级后进行修改保存耗时如下: ...

February 19, 2019 · 1 min · jiezi

如何编写简单的parser(实践篇)

上一篇(《如何编写简单的parser(基础篇)》)中介绍了编写一个parser所需具备的基础知识,接下来,我们要动手实践一个简单的parser,既然是“简单”的parser,那么,我们就要为这个parser划定范围,否则,完整的JavaScript语言parser的复杂度就不是那么简单的了。划定范围基于能够编写简单实用的JavaScript程序和具备基础语法的解释能力这两点考虑,我们将parser的规则范围划分如下:声明:变量声明 & 函数声明赋值:赋值操作 (& 左表达式)加减乘除:加减操作 & 乘除操作条件判断:if语句如果用一句话来划分的话,即一个能解析包括声明、赋值、加减乘除、条件判断的解析器。功能划分基于上一篇中介绍的JavaScript语言由词组(token)组成表达式(expression),由表达式组成语句(statement)的模式,我们将parser划分为——负责解析词法的TokenSteam模块,负责解析表达式和语句的Parser,另外,负责记录读取代码位置的InputSteam模块。这里,有两点需要进行说明:由于我们这里包含的expression解析类型和statement的解析类型都不多,所以,我们使用一个parser模块来统一解析,但是在如babel-parser这类完整的parser中,是将expression和statement拆开进行解析的,这里的逻辑仅供参考;另外,这里对词法的解析是逐字进行解析,并没有使用正则表达式进行匹配解析,因为在完整度高的parser中,使用正则匹配词法会提高整体的复杂度。InputSteamInputSteam负责读取和记录当前代码的位置,并把读取到的代码交给TokenSteam处理,其意义在于,当传递给TokenSteam的代码需要进行判读猜测时,能够记录当前读取的位置,并在接下来的操作汇总回滚到之前的读取位置,也能在发生语法错误时,准确指出错误发生在代码段的第几行第几个字符。该模块是功能最简洁的模块,我们只需创建一个类似“流”的对象即可,其中主要包含以下几个方法:peek() —— 阅读下一个代码,但是不会将当前读取位置迁移,主要用于存在不确定性情况下的判读;next() —— 阅读下一个代码,并移动读取位置到下一个代码,主要用于确定性的语法读取;eof() —— 判断是否到当前代码的结束部分;croak(msg) —— 抛出读取代码的错误。接下来,我们看一下这几个方法的实现:function InputStream(input) { var pos = 0, line = 1, col = 0; return { next : next, peek : peek, eof : eof, croak : croak, }; function next() { var ch = input.charAt(pos++); if (ch == “\n”) line++, col = 0; else col++; return ch; } function peek() { return input.charAt(pos); } function eof() { return peek() == “”; } function croak(msg) { throw new Error(msg + " (" + line + “:” + col + “)”); }}TokenSteam我们依据一开始划定的规则范围 —— 一个能解析包括声明、赋值、加减乘除、条件判断的解析器,来给TokenSteam划定词法解析的范围:变量声明 & 函数声明:包含了变量、“var”关键字、“function”关键字、“{}”符号、“()”符号、“,”符号的识别;赋值操作:包含了“=”操作符的识别;加减操作 & 乘除操作:包含了“+”、“-”、“”、“/”操作符的识别;if语句:包含了“if”关键字的识别;字面量(毕竟没有字面量也没办法赋值):包括了数字字面量和字符串字面量。接下来,TokenSteam主要使用InputSteam读取并判读代码,将代码段解析为符合ECMAScript标准的词组流,返回的词组流大致如下:{ type: “punc”, value: “(” } // 符号,包含了()、{}、,{ type: “num”, value: 5 } // 数字字面量{ type: “str”, value: “Hello World!” } // 字符串字面量{ type: “kw”, value: “function” } // 关键字,包含了function、var、if{ type: “var”, value: “a” } // 标识符/变量{ type: “op”, value: “!=” } // 操作符,包含+、-、、/、=其中,不包含空白符和注释,空白符用于分隔词组,对于已经解析了的词组流来说并无意义,至于注释,在我们简单的parser中,就不需要解析注释来提高复杂度了。有了需要判读的词组,我们只需根据ECMAScript标准的定义,进行适当的简化,便能抽取出对应词组需要的判读规则,大致逻辑如下:首先,跳过空白符;如果input.eof()返回true,则结束判读;如果input.peek()返回是一个“"”,接下来,读取一个字符串字面量;如果input.peek()返回是一个数字,接下来,读取一个数字字面量;如果input.peek()返回是一个字母,接下来,读取的可能是一个标识符,也可能是一个关键字;如果input.peek()返回是标点符号中的一个,接下来,读取一个标点符号;如果input.peek()返回是操作符中的一个,接下来,读取一个操作符;如果没有匹配以上的条件,则使用input.croak()抛出一个语法错误。以上的,即是TokenSteam工作的主要逻辑了,我们只需不断重复以上的判断,即能成功将一段代码,解析成为词组流了,将该逻辑整理为代码如下:function read_next() { read_while(is_whitespace); if (input.eof()) return null; var ch = input.peek(); if (ch == ‘"’) return read_string(); if (is_digit(ch)) return read_number(); if (is_id_start(ch)) return read_ident(); if (is_punc(ch)) return { type : “punc”, value : input.next() }; if (is_op_char(ch)) return { type : “op”, value : read_while(is_op_char) }; input.croak(“Can’t handle character: " + ch);}主逻辑类似于一个分发器(dispatcher),识别了接下来可能的工作之后,便将工作分发给对应的处理函数如read_string、read_number等,处理完成后,便将返回结果吐出。需要注意的是,我们并不需要一次将所有代码全部解析完成,每次我们只需将一个词组吐给parser模块进行处理即可,以避免还没有解析完词组,就出现了parser的错误。为了使大家更清晰的明确词法解析器的工作,我们列出数字字面量的解析逻辑如下:// 使用正则来判读数字function is_digit(ch) { return /[0-9]/i.test(ch);}// 读取数字字面量function read_number() { var has_dot = false; var number = read_while(function(ch){ if (ch == “.”) { if (has_dot) return false; has_dot = true; return true; } return is_digit(ch); }); return { type: “num”, value: parseFloat(number) };}其中read_while函数在主逻辑和数字字面量中都出现了,该函数主要负责读取符合格则的一系列代码,该函数的代码如下:function read_while(predicate) { var str = “”; while (!input.eof() && predicate(input.peek())) str += input.next(); return str;}最后,TokenSteam需要将解析的词组吐给Parser模块进行处理,我们通过next()方法,将读取下一个词组的功能暴露给parser模块,另外,类似TokenSteam需要判读下一个代码的功能,parser模块在解析表达式和语句的时候,也需要通过下一个词组的类型来判读解析表达式和语句的类型,我们将该方法也命名为peek()。function TokenStream(input) { var current = null; function peek() { return current || (current = read_next()); } function next() { var tok = current; current = null; return tok || read_next(); } function eof() { return peek() == null; } // 主代码逻辑 function read_next() { //…. } // … return { next : next, peek : peek, eof : eof, croak : input.croak }; }在next()函数中,需要注意的是,因为有可能在之前的peek()判读中,已经调用read_next()来进行判读了,所以,需要用一个current变量来保存当前正在读的词组,以便在调用next()的时候,将其吐出。Parser最后,在Parser模块中,我们对TokenSteam模块读取的词组进行解析,这里,我们先讲一下最后Parser模块输出的内容,也就是上一篇当中讲到的抽象语法树(AST),这里,我们依然参考babel-parser的AST语法标准,在该标准中,代码段都是被包裹在Program节点中的(其实也是大部分AST标准的模式),这也为我们Parser模块的工作指明了方向,即自顶向下的解析模式:function parse_toplevel() { var prog = []; while (!input.eof()) { prog.push(parse_statement()); } return { type: “prog”, prog: prog };}该parse_toplevel函数,即是Parser模块的主逻辑了,逻辑也很简单,代码段既然是有语句(statements)组成的,那么我们就不停地将词组流解析为语句即可。parse_statement和TokenSteam类似的是,parse_statement也是一个类似于分发器(dispatcher)的函数,我们根据一个词组来判读接下来的工作:function parse_statement() { if(is_punc(”;")) skip_punc(";"); else if (is_punc("{")) return parse_block(); else if (is_kw(“var”)) return parse_var_statement(); else if (is_kw(“if”)) return parse_if_statement(); else if (is_kw(“function”)) return parse_func_statement(); else if (is_kw(“return”)) return parse_ret_statement(); else return parse_expression();}当然,这样的分发模式,也是只限定于我们在最开始划定的规则范围,得益于规则范围小的优势,parse_statement函数的逻辑得以简化,另外,虽然语句(statements)是由表达式(expressions)组成的,但是,表达式(expression)依然能单独存在于代码块中,所以,在parse_statement的最后,不符合所有语句条件的情况,我们还是以表达式进行解析。parse_function在语句的解析中,我们拿函数的的解析来作一个例子,依据AST标准的定义以及ECMAScript标准的定义,函数的解析规则变得很简单:function parse_function(isExpression) { skip_kw(“function”); return { type: isExpression?“FunctionExpression”:“FunctionDeclaration”, id: is_punc("(")?null:parse_identifier(), params: delimited("(", “)”, “,”, parse_identifier), body: parse_block() };}对于函数的定义:首先一定是以关键字“function”开头;其后,若是匿名函数,则没有函数名标识符,否则,则解析一个标识符;接下来,则是函数的参数,包含在一对“()”中,以“,”间隔;最后,即是函数的函数体。在代码中,解析参数的函数delimited是依据传入规则,在起始符与结束符之间,以间隔符隔断的代码段来进行解析的函数,其代码如下:function delimited(start, stop, separator, parser) { var res = [], first = true; skip_punc(start); while (!input.eof()) { if (is_punc(stop)) break; if (first) first = false; else skip_punc(separator); if (is_punc(stop)) break; res.push(parser()); } skip_punc(stop); return res;}至于函数体的解析,就比较简单了,因为函数体即是多段语句,和程序体的解析是一致的,ECMAScript标准的定义也很清晰:function parse_block() { var body = []; skip_punc("{"); while (!is_punc("}")) { var sts = parse_statement() sts && body.push(sts); } skip_punc("}"); return { type: “BlockStatement”, body: body }}parse_atom & parse_expression接下来,语句的解析能力具备了,该轮到解析表达式了,这部分,也是整个Parser比较难理解的一部分,这也是为什么将这部分放到最后的原因。因为在解析表达式的时候,会遇到一些不确定的过程,比如以下的代码:(function(a){return a;})(a)当我们解析完成第一对“()”中的函数表达式后,如果此时直接返回一个函数表达式,那么后面的一对括号,则会被解析为单独的标识符。显然这样的解析模式是不符合JavaScript语言的解析模式的,这时,往往我们需要在解析完一个表达式后,继续往后进行尝试性的解析。这一点,在parse_atom和parse_expression中都有所体现。回到正题,parse_atom也是一个分发器(dispatcher),主要负责表达式层面上的解析分发,主要逻辑如下:function parse_atom() { return maybe_call(function(){ if (is_punc("(")) { input.next(); var exp = parse_expression(); skip_punc(")"); return exp; } if (is_kw(“function”)) return parse_function(true) var tok = input.next(); if (tok.type == “var” || tok.type == “num” || tok.type == “str”) return tok; unexpected(); });}该函数一开头便是以一个猜测性的maybe_call函数开头,正如上我们解释的原因,maybe_call主要是对于调用表达式的一个猜测,一会我们在来看这个maybe_call的实现。parse_atom识别了位于“()”符号中的表达式、函数表达式、标识符、数字和字符串字面量,若都不符合以上要求,则会抛出一个语法错误。parse_expression的实现,主要处理了我们在最开始规则中定义的加减乘除操作的规则,具体实现如下:function parse_expression() { return maybe_call(function(){ return maybe_binary(parse_atom(), 0); });}这里又出现了一个maybe_binary的函数,该函数主要处理了加减乘除的操作,这里看到maybe开头,便能知道,这里也有不确定的判断因素,所以,接下来,我们统一讲一下这些maybe开头的函数。maybe_这些以maybe开头的函数,如我们以上讲的,为了处理表达式的不确定性,需要向表达式后续的语法进行试探性的解析。maybe_call函数的处理非常简单,它接收一个用于解析当前表达式的函数,并对该表达式后续词组进行判读,如果后续词组是一个“(”符号词组,那么该表达式一定是一个调用表达式(CallExpression),那么,我们就将其交给parse_call函数来进行处理,这里,我们又用到之前分隔解析的函数delimited。// 推测表达式是否为调用表达式function maybe_call(expr) { expr = expr(); return is_punc("(") ? parse_call(expr) : expr;}// 解析调用表达式function parse_call(func) { return { type: “call”, func: func, args: delimited("(", “)”, “,”, parse_expression), };}由于解析加、减、乘、除操作时,涉及到不同操作符的优先级,不能使用正常的从左至右进行解析,使用了一种二元表达式的模式进行解析,一个二元表达式包含了一个左值,一个右值,一个操作符,其中,左右值可以为其他的表达式,在后续的解析中,我们就能根据操作符的优先级,来决定二元的树状结构,而二元的树状结构,就决定了操作的优先级,具体的优先级和maybe_binary的代码如下:// 操作符的优先级,值越大,优先级越高var PRECEDENCE = { “=”: 1, “||”: 2, “&&”: 3, “<”: 7, “>”: 7, “<=”: 7, “>=”: 7, “==”: 7, “!=”: 7, “+”: 10, “-”: 10, “”: 20, “/”: 20, “%”: 20,};// 推测是否是二元表达式,即看该左值接下来是否是操作符function maybe_binary(left, my_prec) { var tok = is_op(); if (tok) { var his_prec = PRECEDENCE[tok.value]; if (his_prec > my_prec) { input.next(); return maybe_binary({ type : tok.value == “=” ? “assign” : “binary”, operator : tok.value, left : left, right : maybe_binary(parse_atom(), his_prec) }, my_prec); } } return left;}需要注意的是,maybe_binary是一个递归处理的函数,在返回之前,需要将当前的表达式以当前操作符的优先级进行二元表达式的解析,以便包含在另一个优先级较高的二元表达式中。为了让大家更方便理解二元的树状结构如何决定优先级,这里举两个例子:// 表达式一1+23// 表达式二12+3这两段加法乘法表达式使用上面的方法解析后,分别得到如下的AST:// 表达式一{ type : “binary”, operator : “+”, left : 1, right : { type: “binary”, operator: “”, left: 2, // 这里简化了左右值的结构 right: 3 }}// 表达式二{ type : “binary”, operator : “+”, left : { type : “binary”, operator : “”, left : 1, right : 2 }, right : 3}可以看到,经过优先级的处理后,优先级较为低的操作都被处理到了外层,而优先级高的部分,则被处理到了内部,如果你还感到迷惑的话,可以试着自己拿几个表达式进行处理,然后一步一步的追踪代码的执行过程,便能明白了。总结其实,说到底,简单的parser复杂度远比完整版的parser低很多,如果想要更进一步的话,可以尝试去阅读babel-parser的源码,相信,有了这两篇文章的铺垫,babel的源码阅读起来也会轻松不少。另外,在文章的最后,附上该篇文章的demo。参考几篇可以参考的原文,推荐大伙看看:《How to implement a programming language in JavaScript》(http://lisperator.net/pltut/)《Parsing in JavaScript: Tools and Libraries》(https://tomassetti.me/parsing…)标准以及文献:《ECMAScript® 2016 Language Specification》(http://www.ecma-international…)the core @babel/parser (babylon) AST node types(https://github.com/babel/babe…) ...

January 30, 2019 · 4 min · jiezi

babel基础配置

babel标签(空格分隔): babelbabelBabel是一个广泛使用的转码器,可以将ES6代码转为ES5代码,从而在现有环境执行。Babel 会在正在被转录的文件的当前目录中查找一个 .babelrc 文件。 如果不存在,它会遍历目录树,直到找到一个 .babelrc 文件,或一个 package.json 文件中有 “babel”: {}babel6将babel全家桶拆分成了许多不同的模块(rc是run command的缩写)依赖babel-loader:使用es6或加载模块时,对es6代码进行预处理,转为es5语法。babel-core:允许我们去调用babel的api,可以将js代码分析成ast(抽象语法树),方便各个插件分析语法进行相应的处理.babel-preset-env:推荐preset,比如es2015,es2016,es2017,latest,env(包含前面全部)babel-polyfill:它效仿一个完整的ES2015+环境,使得我们能够使用新的内置对象比如 Promise,比如 Array.prototype.includes 和生成器函数(提供给你使用 regenerator 插件)。为了达到这一点, polyfill 添加到了全局范围,就像原生类型比如 String 一样。babel-runtime babel-plugin-transform-runtime:这个插件能自动为项目引入polyfill和helperspresetsbabel5会默认转译ES6和jsx语法,babel6转译的语法都要在perset中配置,preset简单说就是一系列plugin包的使用。预设就是一系列插件的集合,把之前的参数保存为一个预设,下次就能直接使用。基础配置如下:(设置转码规则和插件){ “presets”: [], “plugins”: []}presets字段设定转码规则,官方提供以下的规则集,按需安装。可以看到提案在进入stage3阶段时就已经在一些环境被实现,在stage2阶段有babel的实现。# ES2015转码规则babel-preset-es2015# react转码规则babel-preset-react# ES7不同阶段语法提案的转码规则(共有4个阶段)babel-preset-stage-0babel-preset-stage-1: draft - 必须包含2个实验性的具体实现,其中一个可以是用转译器实现的,例如Babel。babel-preset-stage-2: candidate - 至少要有2个符合规范的具体实现。babel-preset-stage-3配置:{ “presets”: [ “es2015”, “stage-2” ], “plugins”: [] }babel-preset-env此段内容来自于babel到底该如何配置?上面这些preset官方现在都已经不推荐了,官方唯一推荐preset:babel-preset-env这款preset能灵活决定加载哪些插件和polyfill// cnpm install -D babel-preset -env{ “presets”: [ [“env”, { “targets”: { //指定要转译到哪个环境 //浏览器环境 “browsers”: [“last 2 versions”, “safari >= 7”], //node环境 “node”: “6.10”, //“current” 使用当前版本的node }, //是否将ES6的模块化语法转译成其他类型 //参数:“amd” | “umd” | “systemjs” | “commonjs” | false,默认为’commonjs’ “modules”: ‘commonjs’, //是否进行debug操作,会在控制台打印出所有插件中的log,已经插件的版本 “debug”: false, //强制开启某些模块,默认为[] “include”: [“transform-es2015-arrow-functions”], //禁用某些模块,默认为[] “exclude”: [“transform-es2015-for-of”], //是否自动引入polyfill,开启此选项必须保证已经安装了babel-polyfill //参数:Boolean,默认为false. “useBuiltIns”: false }] ]}{ “presets”: [ [“env”, { “modules”: false, “targets”: { “browsers”: ["> 1%", “last 2 versions”, “not ie <= 8”] } }], “stage-2” ], “plugins”: [ “transform-vue-jsx”, “transform-runtime”, “syntax-dynamic-import”, “transform-es2015-modules-commonjs” ]}plugins此段内容来自于babel到底该如何配置?babel中的插件,通过配置不同的插件才能告诉babel,我们的代码中有哪些是需要转译的。插件官网{ “plugins”: [ [“transform-es2015-arrow-functions”, { “spec”: true }] ]}transform-runtime,这个插件能自动为项目引入polyfill和helperspolyfill作用是用已经存在的语法和api实现一些浏览器还没有实现的api,对浏览器的一些缺陷做一些修补。例如Array新增了includes方法,但是低版本的浏览器上没有,就得做兼容处理transform-runtime这个插件依赖于babel-runtimebabel-runtime由三个部分组成:core-jscore-js极其强悍,通过ES3实现了大部分的ES5、6、7的polyfill。regeneratorregenerator来自facebook的一个库,用于实现 generator functions。helpersbabel的一些工具函数,这个helpers和使用babel-external-helpers生成的helpers是同一个东西配置transform-runtime{ “plugins”: [ [“transform-runtime”, { “helpers”: false, //自动引入helpers “polyfill”: false, //自动引入polyfill(core-js提供的polyfill) “regenerator”: true, //自动引入regenerator }] ]}比较transform-runtime与babel-polyfill引入垫片的差异:使用runtime是按需引入,需要用到哪些polyfill,runtime就自动帮你引入哪些,不需要再手动一个个的去配置plugins,只是引入的polyfill不是全局性的,有些局限性。而且runtime引入的polyfill不会改写一些实例方法,比如Object和Array原型链上的方法,像前面提到的Array.protype.includes。babel-polyfill就能解决runtime的那些问题,它的垫片是全局的,而且全能,基本上ES6中要用到的polyfill在babel-polyfill中都有,它提供了一个完整的ES6+的环境。babel官方建议只要不在意babel-polyfill的体积,最好进行全局引入,因为这是最稳妥的方式。一般的建议是开发一些框架或者库的时候使用不会污染全局作用域的babel-runtime,而开发web应用的时候可以全局引入babel-polyfill避免一些不必要的错误,而且大型web应用中全局引入babel-polyfill可能还会减少你打包后的文件体积(相比起各个模块引入重复的polyfill来说)。结合ESLint许多工具需要Babel进行前置转码,如ESLint和Mocha在项目根目录下,新建一个配置文件.eslint,在其中加入parser字段。{ “parser”: “babel-eslint”, “rules”: { … }}在package.json之中,加入相应的scripts脚本"scripts": { “lint”: “eslint –ext .js,.vue src”,}, ...

January 25, 2019 · 1 min · jiezi

如何编写简单的parser(基础篇)

什么是parser?简单的说,parser的工作即是将代码片段转换成计算机可读的数据结构的过程。这个“计算机可读的数据结构”更专业的说法是“抽象语法树(abstract syntax tree)”,简称AST。AST是代码片段具体语义的抽象表达,它不包含该段代码的所有细节,比如缩进、换行这些细节,所以,我们可以使用parser转换出AST,却不能使用AST还原出“原”代码,当然,可以还原出语义一致的代码,就如同将ES6语法的js代码转换成ES5的代码。parser的结构一般来说,一个parser会由两部分组成:词法解析器(lexer/scanner/tokenizer)对应语法的解释器(parser)在解释某段代码的时候,先由词法解释器将代码段转化成一个一个的词组流(token),再交由解释器对词组流进行语法解释,转化为对应语法的抽象解释,即是AST了。为了让大家更清楚的理解parser两部分的工作顺序,我们通过一个例子来进行说明:437 + 734在parser解析如上的计算表达式时,词法解析器首先依次扫描到“4”、“3”、“7”直到一个空白符,这时,词法解析器便将之前扫描到的数字组成一个类型为“NUM”的词组(token);接下来,词法解析器继续向下扫描,扫描到了一个“+”,对应输出一个类型为“PLUS”的词组(token);最后,扫描“7”、“3”、“4”输出另一个类型为“NUM”的词组(token)。语法解释器在拿到词法解析器输出的词组流后,根据词组流的“NUM”,“PLUS”,“NUM”的排列顺序,解析成为加法表达式。由上的例子我们可以看出,词法解析器根据一定的规则对字符串进行解析并输出为词组(token),具体表现为连续不断的数字组合(“4”、“3”、“7”和“7”、“3”、“4”)即代表了数字类型的词组;语法解释器同样根据一定的规则对词组的组合进行解析,并输出对应的表达式或语句。在这里,词法解析器应用的规则即为词汇语法(Lexical Grammar)的定义,语法解释器应用的规则即为表达式(Expressions)、语句(Statements)、声明(Declarations)和函数(Functions)等的定义。ECMAScript标准看到这里大家可能会感觉到奇怪,为什么讲parser讲的好好的,又跑到ECMAScript标准上来了呢?因为以上提到的词汇语法(Lexical Grammar)、表达式(Expressions)、语句(Statements)、声明(Declarations)和函数(Functions)等都是ECMAScript标准中的所定义的,这其实也是ECMAScript标准的作用之一,即定义JavaScript的标准语法。词汇词法(Lexical Grammar)ECMAScript的词汇词法规定了JavaScript中的基础语法,比如哪些字符代表了空白(White Space),哪些字符代表了一行终止(Line Terminators),哪些字符的组合代表了注释(Comments)等。具体的规定说明,可以在ECMAScript标准11章中找到。这里我们不仔细研读每个语法的定义,只需知道词法解析器(lexer)判读词组(token)的依据来源于此即可,为了让大家有一定的了解,这里,我们拿上面例子中的数字字面量(Numeric Literals)来进行说明:ECMAScript标准中,对数字字面量的定义如下:该定义需要自上向下解读:首先,规则定义了数字字面量(Numeric Literal)可以是十进制字面量(Decimal Literal)、二进制整数字面量(Binary Integer Literal)、八进制整数字面量(Octal Integer Literal)、十六进制整数字面量(Hex Integer Literal);在我们的例子中,我们只关心十进制的字面量,所以,接下来,规则定义十进制字面量(Decimal Literal)可以是包含小数点与不包含小数点的组合,这里我们只需关注不包含小数点的定义,即十进制整数字面量(Decimal Integer Literal) + 可选的指数部分(Exponent Part);最后,规则定义十进制整数字母量由非零数字(Non Zero Digit)+ 十进制数字(Decimal Digit)或十进制数字组(Decimal Digits)组成,非零数字是由19的数字组成,十进制数字是由09组成。将上面的定义重新整合后,就能得到我们需要的数字字面量的定义规则:非零数字(19)+十进制数字组(09)需要注意的是,这是简化版的数字字面量定义,完整版的需要加上以上规则中的所有分支条件。表达式(Expressions)、语句(Statements)ECMAScript标准12~13章包含了表达式和语句的相关定义,之前由词法解析器(lexer)处理后生成的词组流(token)交由语法解释器(parser)处理的主要内容,即是处理词组流构成的表达式与语句。在这里,我们需要稍加明确一下表达式与语句之间的不同与关系:首先,语句包含表达式,大部分语句是由关键字+表达式或语句组成,而表达式则是由字面量(Literal)、标识符(Identifier)、符号(Punctuators)等低一级的词组组成;其次,表达式一般来讲会产生一个值,而语句不总有值。理解第一点对于我们写语法解释器很重要,由于语句是由表达式组成的,而表达式是有词组组成的,词组是有词法解析器进行解析生成的,所以,在语法解释器中,将以表达式为切入点,由表达式解析再深入到语句解析中。抽象语法树(AST)了解一个parser的结构,以及parser解析语法所依赖的规则后,接下来,我们需要了解一下一个parser所生产出来的结果——抽象语法树。在文章的开头,我有简单的解释抽象语法树即是具体代码片段的抽象表达,那它具体是长什么样的呢?function sum (a , b) { return a+b;}以上的代码片段,AST树的描述如下(使用babylon7-7.0.0-beta.44,结果进行了简化):{ “type”: “Program”, “body”: [ { “type”: “FunctionDeclaration”, “id”: { “type”: “Identifier”, “name”: “sum” }, “params”: [ { “type”: “Identifier”, “name”: “a” }, { “type”: “Identifier”, “name”: “b” } ], “body”: { “type”: “BlockStatement”, “body”: [ { “type”: “ReturnStatement”, “argument”: { “type”: “BinaryExpression”, “left”: { “type”: “Identifier”, “name”: “a” }, “operator”: “+”, “right”: { “type”: “Identifier”, “name”: “b” } } } ] } } ]}对该AST仔细观察一番,便会明白,AST其实即是我们在已经ECMAScript标准对代码进行解析后,将标识符(identifier)、声明(declaration)、表达式(expression)、语句(statement)等按代码表述的逻辑整理成为树状结构。就拿上面的例子来说,当语法解析器识别了一个二元表达式(Binary Expression),便将这个二元表达式所携带的信息——左值,右值,操作符按照固定的计算机可读的数据格式保存下来,即是我们看到的AST树了。当然,AST也需要具备固定的格式,这样计算机才能依照该格式阅读AST并进行接下来的编译工作,当然,有一些AST也被用来转义(如babel)。关于AST定义的规则,我们可以参考babel的定义,这也是后面我们实现parser时,所参考的标准。接下来理解完以上相关的知识,我们便具备编写一个parser的先决条件了,那在下一章,我们将实际操作一番,编写一个简易版本的JavaScript语言parser。 ...

January 22, 2019 · 1 min · jiezi

babel preset env配置

babel的配置可以写在 package.json中,如下面// .babelrc{ “babel”: { presets: ["@babel/preset-env", “@babel/preset-stage-2”] }}也可以写在一个.babelrc的JSON配置文件中{ “presets”: ["@babel/preset-env", “@babel/preset-stage-2”]}@babel/preset-env配置字段你可以像上面一样直接指定presets为["@babel/preset-env", “@babel/preset-stage-2”],也可以为每一个preset添加配置选项。(使用数组,第一个元素表示preset的名字,第二个元素表示配置项){ “presets”: [ ["@babel/preset-env", { }], “@babel/preset-stage-2” ]}presets 的编译顺序是反向的,因此你应该把"stage-2"放在"env"后面。preset-env.targets{ “presets”: [ ["@babel/preset-env": { “targets” : “last 2 versions, not dead” }] ]}// 或者直接指定需要兼容的浏览器{ “presets”: [ [“env”: { “targets” : { “chrome”: “58”, “ie”: “11” } }] ]}// 或者是在targets.browsers 中指定{ “presets”: [ [“env”: { “targets” : { “browsers”: “last 2 versions”, “esmodules”: true, // 指定该选项,将会忽略browserslist, 仅支持那些那些原生支持es6 module的浏览器 “safari”: true , // 启用safari前沿技术 “node”: “true” || “current” //兼容当前node版本代码 } }] ]}browserslistbabel使用的浏览器查询是来自于browserslist项目。这个项目同时被很多前端工具库诸如auto-prefixer,postcss使用。最好的指定browserslist的方法是使用一个.browserslistrc 配置文件。它可以被项目中多种工具共享。last 2 vsersionsnot dead你可以使用npx browserslist 来查看你查看当前项目的browserslisg或者是在browserslist online tool 工具中查看preset-env.modules"amd" | “umd” | “systemjs” | “commonjs” | “cjs” | “auto” | false, defaults to “auto"指定将es6 modules 转换为何种模块规范。一般在webpack 项目中,我们会将此参数设置为false,既将module交由webpack处理,而不是babel。如果设置为false, 将不转换。这在node项目中如mocha测试中将不会转换import/export,可能会出错preset-env.userBuiltIns"usage” | “entry” | false, defaults to falseThis option configures how @babel/preset-env handles polyfills.“entry”: 在入口文件中加入所有的内置类型如果在.babelrc中指定useBuiltIns: ’entry’, 则应该在项目代码的顶部引入babel-polyfillimport “@babel/polyfill"表示全量引入。“usage”: 只在当前文件中加入该文件用到的内置类型的polyfill。设置为usage 不需要在顶部引入polyfill"false”: 不自动加入内置类型的polyfill。该特性babel 7 中有效, 测试地址参考再见preset-2015一口气了解Babel ...

January 17, 2019 · 1 min · jiezi

Babel的简单使用

Babel的简单使用1.初始化,创建package.json npm init2.安装babel-cli npm i -d babel-cli3.安装babel npm i -d babel-preset-latest4.添加 .babelrc 文件a.创建 type nul>.babelb.内容 { “presets”:[“latest”], “plugins”:[] }5.创建src、release 文件夹6.修改package.json { … “scripts”: { … “build”:“babel src -d release” }, }“babel js -d release” 可理解为"把src文件夹下的js文件转换后存到release文件夹下"7.编写示例代码( src/test.js) let arr=[1,3,5,6,8,9]; 8.运行 npm run build > es6test@1.0.0 build D:\webWork\es6test > babel src -d release src\test.js -> release\test.js9.结果(release/test.js) “use strict”; var arr = [1, 3, 5, 6, 8, 9];

January 3, 2019 · 1 min · jiezi

babel的一些常用知识点整理

关于bable的一些知识点整理babel-polyfill 的作用 和 runtime babel-cli babel-corebabel-preset-env babel-preset-preset-stage-2babel-loaderbabel使用方法:使用单体文件使用命令行构建工具的插件运行方式和插件babel总共分为3个阶段: 解析,转换和生成babel本身不具有任何转换功能, 如果没有plugin,那么经过babel的代码和输入的是相同的。babel插件分为两种语法插件:在解析的过程中,能使babel能够解析更多的语法转译插件: 在转换的过程中将代码输出。比如将箭头函数转译成正常的函数用了转译插件后,就不需要用语法插件了,因为同一语法可能同时存在语法插件和转译插件。常用的一些插件问题presetpreset是一套规范, 里面包含了几十个转译插件。这是一组插件的集合preset可以分为下面几种:按官方内容: env, react, flow, minifystage-x, 包含当年最新规范的草案,每年更新每个stage是不一样的,可以分为以下几点Stage 0 - 稻草人: 只是一个想法,经过 TC39 成员提出即可。Stage 1 - 提案: 初步尝试。Stage 2 - 初稿: 完成初步规范。Stage 3 - 候选: 完成规范和浏览器初步实现。Stage 4 - 完成: 将被添加到下一年度发布。低一级的stage会包含所有高级stage的内容,stage-1包含stage-2,stage-3的所有内容。stage-4 在下一年更新会直接放到env中,所以没有单独的stage-4可供使用。 env是一个每年更新的preset.执行顺序plugin会运行在Preset之前plugin从前到后顺序执行preset的顺序是从后向前插件和 preset 只要列出字符串格式的名字即可。但如果某个 preset 或者插件需要一些配置项(或者说参数),就需要把自己先变成数组。第一个元素依然是字符串,表示自己的名字;第二个元素是一个对象,即配置对象。“presets”: [ // 带了配置项,自己变成数组 [ // 第一个元素依然是名字 “env”, // 第二个元素是对象,列出配置项 { “module”: false } ], // 不带配置项,直接列出名字 “stage-2”]babel-clicli是命令工具,安装了之后,就能够在命令行中使用babel命令来编译文件所以babel-cli 安装为 devDependencies### babel-nodebabel-node是babel-cli的一部分,不需要独立安装他使得能够在node环境中,直接运行es2015的代码,不需要额外进行转码。babel-node = babel-polyfill + babel-register### babel-registerbabel-register 改写require命令, 为它加上一个钩子。每当使用require加载.js、.jsx、.es 和 .es6 后缀名的文件, 就会先用babel进行转码。使用时,必须先加载require(‘babel-register’)。但是babel-register只会对加载的文件转码,对当前文件是不会骑左右的。而且他属于实时转码,只适用于开发环境使用。### babel-polyfillbabel只转换js语法,不转换API,如Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局对象,以及一些定义在全局对象上的方法(比如 Object.assign)都不会转码。但是可以用babel-polyfill进行转码。使用时,在所有代码运行之前增加 require(‘babel-polyfill’)。或者更常规的操作是在 webpack.config.js 中将 babel-polyfill 作为第一个 entry。因此必须把 babel-polyfill 作为 dependencies 而不是 devDependencies但是他的缺点是:使用babel-polyfill 会导致打出来的包非常大。babel-polyfill 是一个整体,把所有的方法都加到原型链上。如果我们只使用了Array.from,但是他会把Object.defineProperty也给加上。babel-polyfill会污染全局变量,给很多类的原型链上都作了修改。所以在实际过程中,通常倾向于用babel-plugin-transform-runtime但是如果代码中有高版本的js中类型的实例方法([1,2,3].includes(1)),就只能用polyfill了。这个相当于是垫片。babel-runtime 和 babel-plugin-transform-runtimebable 会转译js语法,举个async/await例子// babel 添加一个方法,把 async 转化为 generatorfunction _asyncToGenerator(fn) { return function () {….}} // 很长很长一段// 具体使用处var _ref = _asyncToGenerator(function* (arg1, arg2) { yield (0, something)(arg1, arg2);});这里_asyncToGenerator在当前文件被定义的,然后被使用了。如果每个文件都有用async/await方法,那么每个文件都会有_asyncToGenerator 这段,会导致重复和浪费。用了babel-plugin-transform-runtime之后,转换的代码就变成// 从直接定义改为引用,这样就不会重复定义了。var _asyncToGenerator2 = require(‘babel-runtime/helpers/asyncToGenerator’);var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2);// 具体使用处是一样的var _ref = _asyncToGenerator3(function* (arg1, arg2) { yield (0, something)(arg1, arg2);});这里就相当于引用了一个模块代码,就不存在重复的问题了。所以这就是用babel-plugin-transform-runtime的好处,而babel-runtime就是为这些方法提供了集合。所以在使用babel-plugin-transform-runtime的时候,需要babel-runtime做依赖。babel-runtimebabel-runtime 内部集成了core-js: 转换一些内置类(Promise, Symbol)和静态方法(Array.from等)。绝大部分的引用都是这里做的,自动引入。regenerator: 是作为core-js的拾遗补漏,主要是generator/yield和async/await两组做支持。当代码中有generators/async时会自动引入。helpersbabel-plugin-transform-runtime 不支持 实例方法 (例如 [1,2,3].includes(1))babel-loader主要使用在构建工具中。babel-loader和babel-cli一样,会读取.babelrc或者package.json中的babel段作为自己的配置,但是babel-loader必须要和webpack做交互。名称作用备注babel-cli允许命令行使用 babel 命令转译文件 babel-node允许命令行使用 babel-node 直接转译+执行 node 文件随 babel-cli 一同安装.babel-node = babel-polyfill + babel-registerbabel-register改写 require 命令,为其加载的文件进行转码,不对当前文件转码只适用于开发环境babel-polyfill为所有 API 增加兼容方法需要在所有代码之前 require,且体积比较大babel-plugin-transform-runtime & babel-runtime把帮助类方法从每次使用前定义改为统一 require,精简代码babel-runtime 需要安装为依赖,而不是开发依赖babel-loader使用 webpack 时作为一个 loader 在代码混淆之前进行代码转换 一口(很长的)气了解 babel ...

December 29, 2018 · 1 min · jiezi

@babel/polyfill 总结

原文链接 https://babeljs.io/docs/en/ba...@babel/polyfillBabel 包含一个polyfill 库。这个库里包含 regenerator 和 core-js.这个库将会模拟一个完全的 ES2015+ 的环境。这意味着你可以使用 新的内置语法 比如 promise 或者 WeakMap, 静态方法比如Array.from 或 Object.assign, 实例方法 比如 Array.prototype.includes 和 generator 函数。Installationnpm install –save @babel/polyfill这个是在你的source code 前运行的,所以安装的时候是 –savesizepolyfill 用起来很方便,但是你应该和 @babel/preset-env 以及 useBuiltIns option 一起用。这样在使用的时候就不会包含那些我们一般不会用到的polyfill 了。如果不这样做的话,我们还是建议你手动引入你需要的每个polyfillTC39 提案如果你想使用一些不在 Stage 4 中的提案,@babel/polyfill 不会自动帮你引入它们。你可以从 core-js 中单独引入。usage in Node/Browserify/Webpack为了引入polyfill。你需要在你应用的 entry point 的头部引入它确保 它在 其他代码或者 引用前被调用require(’@babel/polyfill’)如果你使用的是es6 的import 语法,你也要在入口点的顶部引入polyfill,以确保首先加载polyfill。import ‘@babel/polyfill’webpack 集成polyfill和@babel/preset-env 一起用的时候如果在.babelrc 中指定 useBuiltIns: ‘usage’的话,那么就不要在webpack.config.js 的 entry array 和source 中包含 @babel/polyfill 了。注意,@babel/polyfill 依然需要安装如果在.babelrc 中指定 useBuiltIns: ’entry’的话,那么就要和上面讨论的一样,在你应用的入口文件顶部通过require 或者 import 引入@babel/polyfill.如果在.babelrc 中没有指定 useBuiltIns 的值或者 设置 useBuiltIns: false. 可以直接在webpack.config.js 的 entry array 中添加 @babel/polyfill module.exports = { entry: [’@babel/polyfill’, ‘./app’] }如果没有使用@babel/preset-env.那么就可以像我们上面讨论的一样把@babel/polyfill 添加到webpack 的entry array 中。你也可以直接通过import 或require 把它添加到应用的入口文件顶部。但是我们并不推荐这么做Usage in Browser在浏览器中使用的话,直接引入@babel/polyfill 发布的文件dist/polyfill.js 就行了。这个文件需要包含在所有你编译好的代码之前。你可以把它放在你编译好的代码之前,也可以放在一个script 标签之中。 ...

December 20, 2018 · 1 min · jiezi

我把自己的经历做了提炼,起草了一个项目:抽丝剥茧的学前端之React篇

三四句话总结全文我今天终于可以自信地说我React入门了!我把这几个月学会的知识点总结在了一个项目里,我将它命名为:抽丝剥茧的学前端之React篇github仓库地址是:[https://github.com/Bedivere-Sun/write_react_project_from_scratch][1]欢迎各位前辈前来指正我理解上的问题,以免误导他人。也希望能够帮助到和我一样爱钻牛角尖的朋友,减少他们的痛苦。 啰嗦的正文大家好,这是我来segmentFault以来第一次鼓起勇气发表自己的原创文章,而且也是第一次推荐自己起草的项目:抽丝剥茧的学前端之React篇。希望多少能够帮助到和我一样或相似在React入门的道路上还在受苦的朋友们。仓库地址https://github.com/Bedivere-S…本项目的目的文档不能直观的把知识都展现出来,因此我将代码和文档都放进来,比照学习通过版本迭代的方式将React尽可能做到理解透彻,最终形成一个没有迷雾的知识库每个大的版本更新都将此前的旧版本打包,以此形成“阶段性的章节”,以免让新人一下接触太多导致混乱为每一个代码文件做注释,详细说明为什么这么写,这是什么原理,以及我自己学习过程中对这块内容的理解第一版的主要知识点介绍webpack的配置方法告诉你为什么要用这些模块,怎么用介绍babel的一些基本内容介绍html-webpack-plugin的内容,具体写法,所有选型的意义简历了一个简单的页面,学会通过引入资源的方式将其内容展示出来心历路程我今年10月报名学习腾讯的React课程,但是一直处于懵8的状况。月中开始到11月以来我基本都是有如没头苍蝇一般四处碰壁。我和学习群里的一些同学还吵过架,原因很简单,我提出一个问题,总会有人说“你没有前端基础,应该看看基础的内容”……我吵回去的理由也很简单很傻:“你说我没基础,那你倒是说说我到底哪里没基础啊,前端基础那么多的领域,我应该看哪个部分的基础啊?”这个问题困扰了我半个多月。后来学习群里有几位前辈给我推荐了一些书,还有的前辈真的认真倾听了我的问题,指出我要学习什么基础知识。11月开始,我列了一个todo list,画了张思维导图,基本上学习计划就非常清晰了。于是开始了各种请教,然后开始猛啃官方文档全文。中英文文档其实还是有很多差距的,一些单词翻译不出来,还有的部分干脆直接被省略了……所以我就只好对照着看,然后还是在群里和各位前辈们钻牛角尖……可以说我11月积累了很多知识,我把这些知识点都收集了起来,然后再根据自己读文档的印象……我突然就觉得自己明白了很多,写简单的项目也没有什么疑虑了。然后到了12月8号,我起草了这个项目,把自己学到的知识点做了一个提炼,希望能够帮助到和我一样曾经感到迷茫的小伙伴们。2018年12月19日

December 19, 2018 · 1 min · jiezi

ES6 系列之模块加载方案

前言本篇我们重点介绍以下四种模块加载规范:AMDCMDCommonJSES6 模块最后再延伸讲下 Babel 的编译和 webpack 的打包原理。require.js在了解 AMD 规范之前,我们先来看看 require.js 的使用方式。项目目录为:* project/ * index.html * vender/ * main.js * require.js * add.js * square.js * multiply.jsindex.html 的内容如下:<!DOCTYPE html><html> <head> <title>require.js</title> </head> <body> <h1>Content</h1> <script data-main=“vender/main” src=“vender/require.js”></script> </body></html>data-main=“vender/main” 表示主模块是 vender 下的 main.js。main.js 的配置如下:// main.jsrequire([’./add’, ‘./square’], function(addModule, squareModule) { console.log(addModule.add(1, 1)) console.log(squareModule.square(3))});require 的第一个参数表示依赖的模块的路径,第二个参数表示此模块的内容。由此可以看出,主模块依赖 add 模块和 square 模块。我们看下 add 模块即 add.js 的内容:// add.jsdefine(function() { console.log(‘加载了 add 模块’); var add = function(x, y) { return x + y; }; return { add: add };});requirejs 为全局添加了 define 函数,你只要按照这种约定的方式书写这个模块即可。那如果依赖的模块又依赖了其他模块呢?我们来看看主模块依赖的 square 模块, square 模块的作用是求出一个数字的平方,比如输入 3 就返回 9,该模块依赖一个乘法模块,该乘法模块即 multiply.js 的代码如下:// multiply.jsdefine(function() { console.log(‘加载了 multiply 模块’) var multiply = function(x, y) { return x * y; }; return { multiply: multiply };});而 square 模块就要用到 multiply 模块,其实写法跟 main.js 添加依赖模块一样:// square.jsdefine([’./multiply’], function(multiplyModule) { console.log(‘加载了 square 模块’) return { square: function(num) { return multiplyModule.multiply(num, num) } };});require.js 会自动分析依赖关系,将需要加载的模块正确加载。requirejs 项目 Demo 地址:https://github.com/mqyqingfeng/Blog/tree/master/demos/ES6/module/requirejs而如果我们在浏览器中打开 index.html,打印的顺序为:加载了 add 模块加载了 multiply 模块加载了 square 模块29AMD在上节,我们说了这样一句话:requirejs 为全局添加了 define 函数,你只要按照这种约定的方式书写这个模块即可。那这个约定的书写方式是指什么呢?指的便是 The Asynchronous Module Definition (AMD) 规范。所以其实 AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。你去看 AMD 规范) 的内容,其主要内容就是定义了 define 函数该如何书写,只要你按照这个规范书写模块和依赖,require.js 就能正确的进行解析。sea.js在国内,经常与 AMD 被一起提起的还有 CMD,CMD 又是什么呢?我们从 sea.js 的使用开始说起。文件目录与 requirejs 项目目录相同:* project/ * index.html * vender/ * main.js * require.js * add.js * square.js * multiply.jsindex.html 的内容如下:<!DOCTYPE html><html><head> <title>sea.js</title></head><body> <h1>Content</h1> <script src=“vender/sea.js”></script> <script> // 在页面中加载主模块 seajs.use("./vender/main"); </script></body></html>main.js 的内容如下:// main.jsdefine(function(require, exports, module) { var addModule = require(’./add’); console.log(addModule.add(1, 1)) var squareModule = require(’./square’); console.log(squareModule.square(3))});add.js 的内容如下:// add.jsdefine(function(require, exports, module) { console.log(‘加载了 add 模块’) var add = function(x, y) { return x + y; }; module.exports = { add: add };});square.js 的内容如下:define(function(require, exports, module) { console.log(‘加载了 square 模块’) var multiplyModule = require(’./multiply’); module.exports = { square: function(num) { return multiplyModule.multiply(num, num) } };});multiply.js 的内容如下:define(function(require, exports, module) { console.log(‘加载了 multiply 模块’) var multiply = function(x, y) { return x * y; }; module.exports = { multiply: multiply };});跟第一个例子是同样的依赖结构,即 main 依赖 add 和 square,square 又依赖 multiply。seajs 项目 Demo 地址:https://github.com/mqyqingfeng/Blog/tree/master/demos/ES6/module/seajs而如果我们在浏览器中打开 index.html,打印的顺序为:加载了 add 模块2加载了 square 模块加载了 multiply 模块9CMD与 AMD 一样,CMD 其实就是 SeaJS 在推广过程中对模块定义的规范化产出。你去看 CMD 规范的内容,主要内容就是描述该如何定义模块,如何引入模块,如何导出模块,只要你按照这个规范书写代码,sea.js 就能正确的进行解析。AMD 与 CMD 的区别从 sea.js 和 require.js 的例子可以看出:1.CMD 推崇依赖就近,AMD 推崇依赖前置。看两个项目中的 main.js:// require.js 例子中的 main.js// 依赖必须一开始就写好require([’./add’, ‘./square’], function(addModule, squareModule) { console.log(addModule.add(1, 1)) console.log(squareModule.square(3))});// sea.js 例子中的 main.jsdefine(function(require, exports, module) { var addModule = require(’./add’); console.log(addModule.add(1, 1)) // 依赖可以就近书写 var squareModule = require(’./square’); console.log(squareModule.square(3))});2.对于依赖的模块,AMD 是提前执行,CMD 是延迟执行。看两个项目中的打印顺序:// require.js加载了 add 模块加载了 multiply 模块加载了 square 模块29// sea.js加载了 add 模块2加载了 square 模块加载了 multiply 模块9AMD 是将需要使用的模块先加载完再执行代码,而 CMD 是在 require 的时候才去加载模块文件,加载完再接着执行。感谢感谢 require.js 和 sea.js 在推动 JavaScript 模块化发展方面做出的贡献。CommonJSAMD 和 CMD 都是用于浏览器端的模块规范,而在服务器端比如 node,采用的则是 CommonJS 规范。导出模块的方式:var add = function(x, y) { return x + y;};module.exports.add = add;引入模块的方式:var add = require(’./add.js’);console.log(add.add(1, 1));我们将之前的例子改成 CommonJS 规范:// main.jsvar add = require(’./add.js’);console.log(add.add(1, 1))var square = require(’./square.js’);console.log(square.square(3));// add.jsconsole.log(‘加载了 add 模块’)var add = function(x, y) { return x + y;};module.exports.add = add;// multiply.jsconsole.log(‘加载了 multiply 模块’)var multiply = function(x, y) { return x * y;};module.exports.multiply = multiply;// square.jsconsole.log(‘加载了 square 模块’)var multiply = require(’./multiply.js’);var square = function(num) { return multiply.multiply(num, num);};module.exports.square = square;CommonJS 项目 Demo 地址:https://github.com/mqyqingfeng/Blog/tree/master/demos/ES6/module/commonJS如果我们执行 node main.js,打印的顺序为:加载了 add 模块2加载了 square 模块加载了 multiply 模块9跟 sea.js 的执行结果一致,也是在 require 的时候才去加载模块文件,加载完再接着执行。CommonJS 与 AMD引用阮一峰老师的《JavaScript 标准参考教程(alpha)》:CommonJS 规范加载模块是同步的,也就是说,只有加载完成,才能执行后面的操作。AMD规范则是非同步加载模块,允许指定回调函数。由于 Node.js 主要用于服务器编程,模块文件一般都已经存在于本地硬盘,所以加载起来比较快,不用考虑非同步加载的方式,所以 CommonJS 规范比较适用。但是,如果是浏览器环境,要从服务器端加载模块,这时就必须采用非同步模式,因此浏览器端一般采用 AMD 规范。ES6ECMAScript2015 规定了新的模块加载方案。导出模块的方式:var firstName = ‘Michael’;var lastName = ‘Jackson’;var year = 1958;export {firstName, lastName, year};引入模块的方式:import {firstName, lastName, year} from ‘./profile’;我们再将上面的例子改成 ES6 规范:目录结构与 requirejs 和 seajs 目录结构一致。<!DOCTYPE html><html> <head> <title>ES6</title> </head> <body> <h1>Content</h1> <script src=“vender/main.js” type=“module”></script> </body></html>注意!浏览器加载 ES6 模块,也使用 <script> 标签,但是要加入 type=“module” 属性。// main.jsimport {add} from ‘./add.js’;console.log(add(1, 1))import {square} from ‘./square.js’;console.log(square(3));// add.jsconsole.log(‘加载了 add 模块’)var add = function(x, y) { return x + y;};export {add}// multiply.jsconsole.log(‘加载了 multiply 模块’)var multiply = function(x, y) { return x * y;};export {multiply}// square.jsconsole.log(‘加载了 square 模块’)import {multiply} from ‘./multiply.js’;var square = function(num) { return multiply(num, num);};export {square}ES6-Module 项目 Demo 地址:https://github.com/mqyqingfeng/Blog/tree/master/demos/ES6/module/ES6值得注意的,在 Chrome 中,如果直接打开,会报跨域错误,必须开启服务器,保证文件同源才可以有效果。为了验证这个效果你可以:cnpm install http-server -g然后进入该目录,执行http-server在浏览器打开 http://localhost:8080/ 即可查看效果。打印的顺序为:加载了 add 模块加载了 multiply 模块加载了 square 模块29跟 require.js 的执行结果是一致的,也就是将需要使用的模块先加载完再执行代码。ES6 与 CommonJS引用阮一峰老师的 《ECMAScript 6 入门》:它们有两个重大差异。CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。第二个差异可以从两个项目的打印结果看出,导致这种差别的原因是:因为 CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。重点解释第一个差异。CommonJS 模块输出的是值的拷贝,也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。举个例子:// 输出模块 counter.jsvar counter = 3;function incCounter() { counter++;}module.exports = { counter: counter, incCounter: incCounter,};// 引入模块 main.jsvar mod = require(’./counter’);console.log(mod.counter); // 3mod.incCounter();console.log(mod.counter); // 3counter.js 模块加载以后,它的内部变化就影响不到输出的 mod.counter 了。这是因为 mod.counter 是一个原始类型的值,会被缓存。但是如果修改 counter 为一个引用类型的话:// 输出模块 counter.jsvar counter = { value: 3};function incCounter() { counter.value++;}module.exports = { counter: counter, incCounter: incCounter,};// 引入模块 main.jsvar mod = require(’./counter.js’);console.log(mod.counter.value); // 3mod.incCounter();console.log(mod.counter.value); // 4value 是会发生改变的。不过也可以说这是 “值的拷贝”,只是对于引用类型而言,值指的其实是引用。而如果我们将这个例子改成 ES6:// counter.jsexport let counter = 3;export function incCounter() { counter++;}// main.jsimport { counter, incCounter } from ‘./counter’;console.log(counter); // 3incCounter();console.log(counter); // 4这是因为ES6 模块的运行机制与 CommonJS 不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令 import,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。换句话说,ES6 的 import 有点像 Unix 系统的“符号连接”,原始值变了,import 加载的值也会跟着变。因此,ES6 模块是动态引用,并且不会缓存值,模块里面的变量绑定其所在的模块。Babel鉴于浏览器支持度的问题,如果要使用 ES6 的语法,一般都会借助 Babel,可对于 import 和 export 而言,只借助 Babel 就可以吗?让我们看看 Babel 是怎么编译 import 和 export 语法的。// ES6var firstName = ‘Michael’;var lastName = ‘Jackson’;var year = 1958;export {firstName, lastName, year};// Babel 编译后’use strict’;Object.defineProperty(exports, “__esModule”, { value: true});var firstName = ‘Michael’;var lastName = ‘Jackson’;var year = 1958;exports.firstName = firstName;exports.lastName = lastName;exports.year = year;是不是感觉有那么一点奇怪?编译后的语法更像是 CommonJS 规范,再看 import 的编译结果:// ES6import {firstName, lastName, year} from ‘./profile’;// Babel 编译后’use strict’;var _profile = require(’./profile’);你会发现 Babel 只是把 ES6 模块语法转为 CommonJS 模块语法,然而浏览器是不支持这种模块语法的,所以直接跑在浏览器会报错的,如果想要在浏览器中运行,还是需要使用打包工具将代码打包。webpackBabel 将 ES6 模块转为 CommonJS 后, webpack 又是怎么做的打包的呢?它该如何将这些文件打包在一起,从而能保证正确的处理依赖,以及能在浏览器中运行呢?首先为什么浏览器中不支持 CommonJS 语法呢?这是因为浏览器环境中并没有 module、 exports、 require 等环境变量。换句话说,webpack 打包后的文件之所以在浏览器中能运行,就是靠模拟了这些变量的行为。那怎么模拟呢?我们以 CommonJS 项目中的 square.js 为例,它依赖了 multiply 模块:console.log(‘加载了 square 模块’)var multiply = require(’./multiply.js’);var square = function(num) { return multiply.multiply(num, num);};module.exports.square = square;webpack 会将其包裹一层,注入这些变量:function(module, exports, require) { console.log(‘加载了 square 模块’); var multiply = require("./multiply"); module.exports = { square: function(num) { return multiply.multiply(num, num); } };}那 webpack 又会将 CommonJS 项目的代码打包成什么样呢?我写了一个精简的例子,你可以直接复制到浏览器中查看效果:// 自执行函数(function(modules) { // 用于储存已经加载过的模块 var installedModules = {}; function require(moduleName) { if (installedModules[moduleName]) { return installedModules[moduleName].exports; } var module = installedModules[moduleName] = { exports: {} }; modules[moduleName](module, module.exports, require); return module.exports; } // 加载主模块 return require(“main”);})({ “main”: function(module, exports, require) { var addModule = require("./add"); console.log(addModule.add(1, 1)) var squareModule = require("./square"); console.log(squareModule.square(3)); }, “./add”: function(module, exports, require) { console.log(‘加载了 add 模块’); module.exports = { add: function(x, y) { return x + y; } }; }, “./square”: function(module, exports, require) { console.log(‘加载了 square 模块’); var multiply = require("./multiply"); module.exports = { square: function(num) { return multiply.multiply(num, num); } }; }, “./multiply”: function(module, exports, require) { console.log(‘加载了 multiply 模块’); module.exports = { multiply: function(x, y) { return x * y; } }; }})最终的执行结果为:加载了 add 模块2加载了 square 模块加载了 multiply 模块9参考《JavaScript 标准参考教程(alpha)》《ECMAScript6 入门》手写一个CommonJS打包工具(一)ES6 系列ES6 系列目录地址:https://github.com/mqyqingfeng/BlogES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。 ...

November 13, 2018 · 5 min · jiezi

webpack4配置详解之逐行分析

前言 经常会有群友问起webpack、react、redux、甚至create-react-app配置等等方面的问题,有些是我也不懂的,慢慢从大家的相互交流中,也学到了不少。 今天就尝试着一起来聊聊Webpack吧,旨在帮大家加深理解、新手更容易上路,都能从0到1搭建配置自定属于自己的脚手架,或对已封装好的脚手架有进一步的巩固,接下来苏南会详细讲解webpack中的每一个配置字段的作用(部分为webpack4新增)。近两年,前端一直在以一个高速持续的过程发展,常常会有网友在调侃老了、学不动了,虽是在调侃却又间接阐述着无奈,迫于生活的压力,不得不提速前行,因为没有谁为你而停留,公司不会、社会不会、同伴不会……,停下可能将意味着淘汰 —— 理想很丰满,现实很骨感,所以让我们一起进步,共同加薪,奋斗吧骚年,加油。。~~吐槽过了,接着聊正事~~。 原谅我控制不住自己,想问下各位,昨天刚刚过去的双十一你脱单了吗?人生若只如初见,何事秋风悲画扇;等闲变却故人心,却道故人心易变;骊山语罢清宵半,夜雨霖铃终不怨。各位大佬早安,这里是@IT·平头哥联盟,我是首席填坑官∙苏南,用心分享 做有温度的攻城狮。公众号:honeyBadger8,群:912594095entry这个不用解释了,看名字就是知道,它就是通往天堂/地狱的入口,一切的苦难从这里开始,也从这里结束。简单介绍几种写法://方式一:单文件写法entry: { index: ‘./src/pages/route.js’, //about: ‘./src/pages/about.js’, //other:()=>{…} //首席填坑官∙苏南的专栏,公众号:honeyBadger8}//方式二:多文件写法entry: { /index:[ //首席填坑官∙苏南的专栏 ‘webpack-hot-middleware/client’, ‘./src/root.js’ ],/ index: [’./src/root.js’], vendors : [‘react’,‘react-dom’,‘redux’,‘react-router’,‘classnames’],}output - 输出它位于对象最顶级键(非常重要),如果说entry是一扇门,output就是审判官,决定着你是上天堂还是入地狱;指示 webpack 如何去输出、以及在哪里输出、输出的格式等;path: 输出文件的目录,filename:输出的文件名,它一般跟你entry配置相对应,如:js/[name].js name在这里表示的是[index、vendors],chunkFilename:块,配置了它,非入口entry的模块,会帮自动拆分文件,也就是大家常说的按需加载,与路由中的 require.ensure相互应publicPath:文件输出的公共路径,pathinfo:即保留相互依赖的包中的注释信息,这个基本不用主动设置它,它默认 development 模式时的默认值是 true,而在 production 模式时的默认值是 false,主要的就是这些,还有一些其他的library、libraryTarget、auxiliaryComment等,感兴趣的可自行了解,output: { path: path.resolve(_dirname, ‘../assets’), filename: ‘js/[name].js’, chunkFilename: ‘js/[name].[chunkhash:8].js’, publicPath: ‘/static/’, //最终访问的路径就是:localhost:3000/static/js/.js //pathinfo:true,}hash常用的有三种:模板描述hash模块标识符的hash,一般应用于filename:’[name].[hash].js’chunkhash按需分块内容的 hash,它是根据chunk自身的内容计算而来contenthash这个没有用过,看了下文档它是在提取css文件时根据内容计算而来的 hash ,结合ExtractTextWebpackPlugin插件使用hash长度默认20,可自定:[hash:8]、[chunkhash:16]mode这个属于webpack4才新增的,4之前大家一般用DefinePlugin插件设置mode:development,production,none,development : 开发模式,打包的代码不会被压缩,开启代码调试,production : 生产模式,则正好反之。//方法一webpack –mode development/production//方法二……mode:‘development/production’……devtool控制是否生成,以及如何生成 source map文件,开发环境下更有利于定位问题,默认 false,当然它的开启,也会影响编译的速度,所以生产环境一定一定记得关闭;常用的值:cheap-eval-source-map、eval-source-map、cheap-module-eval-source-map、inline-cheap-module-source-map等等,更详细的可以去官方查看;本人一般使用:eval-source-map较多,每个都有它不一样的特性,有兴趣的同学可以一一尝试,optimizationoptimization是webpack4新增的,主要是用来让开发者根据需要自定义一些优化构建打包的策略配置,minimize:true/false,告诉webpack是否开启代码最小化压缩,minimizer:自定js优化配置,会覆盖默认的配置,结合UglifyJsPlugin插件使用,removeEmptyChunks: bool 值,它检测并删除空的块。将设置为false将禁用此优化,removeEmptyChunks: bool 值,它检测并删除空的块。将设置为false将禁用此优化,nodeEnv:它并不是node里的环境变量,设置后可以在代码里使用 process.env.NODE_ENV === ‘development’来判断一些逻辑,生产环境UglifyJsPlugin会自动删除无用代码,splitChunks :取代了CommonsChunkPlugin,自动分包拆分、代码拆分,详细默认配置:默认配置,只会作用于异步加载的代码块 —— chunks: ‘async’,它有三个值:all,async,initial//环境变更也可以直接 在启动中设置 //webpack –env.NODE_ENV=local –env.production –progress//splitChunks 默认配置 ,首席填坑官∙苏南的专栏splitChunks: { chunks: ‘async’, minSize: 30000, maxSize: 0, minChunks: 1, maxAsyncRequests: 5, maxInitialRequests: 3, automaticNameDelimiter: ‘~’, name: true, cacheGroups: { vendors: { test: /[\/]node_modules[\/]/, priority: -10 }, default: { minChunks: 2, priority: -20, reuseExistingChunk: true } }}runtimeChunk: 提取 webpack 运行时代码,它可以设置为:boolean、Object该配置开启时,会覆盖 入口指定的名称!!!optimization: { runtimeChunk:true,//方式一 runtimeChunk: { name: entrypoint => runtimechunk~${entrypoint.name} //方式二 }}resolve - 配置模块如何解析extensions:自动解析确定的扩展,省去你引入组件时写后缀的麻烦,alias:非常重要的一个配置,它可以配置一些短路径,modules:webpack 解析模块时应该搜索的目录,其他 plugins、unsafeCache、enforceExtension,基本没有怎么用到,//extensions 后缀可以省略,import Toast from ‘src/components/toast’; // alias ,短路径import Modal from ‘../../../components/modal’ //简写 ,首席填坑官∙苏南的专栏import Modal from ‘src/components/modal’ resolve: { extensions: [’.js’, ‘.jsx’,’.ts’,’.tsx’, ‘.scss’,’.json’,’.css’], alias: { src :path.resolve(__dirname, ‘../src’), components :path.resolve(__dirname, ‘../src/components’), utils :path.resolve(__dirname, ‘../src/utils’), }, modules: [’node_modules’],},module.rules - 编译规则,rules:也就是之前的loaders,test : 正则表达式,匹配编译的文件,exclude:排除特定条件,如通常会写node_modules,即把某些目录/文件过滤掉,include:它正好与exclude相反,use -loader :必须要有它,它相当于是一个 test 匹配到的文件对应的解析器,babel-loader、style-loader、sass-loader、url-loader等等,use - options:它与loader配合使用,可以是一个字符串或对象,它的配置可以直接简写在loader内一起,它下面还有presets、plugins等属性;具体来看一下示例:module: { rules: [ { test: /.(js|jsx)$/, exclude: /node_modules/, use: [ { loader: ‘babel-loader’, options: { presets: [ [’env’, { targets: { browsers: CSS_BROWSERS, }, }],‘react’, ’es2015’, ‘stage-0’ ], plugins: [ ’transform-runtime’, ‘add-module-exports’, ], }, }, ], }, { test: /.(scss|css)$/, use: [ ‘style-loader’, {loader: ‘css-loader’,options:{plugins: [require(‘autoprefixer’)({browsers: CSS_BROWSERS,}),],sourceMap: true}}, {loader: ‘postcss-loader’,options:{plugins: [require(‘autoprefixer’)({browsers: CSS_BROWSERS,}),],sourceMap: true}}, {loader: ‘sass-loader’,options:{sourceMap: true}} ] }, { test: /.(png|jpg|jpeg|gif)$/, exclude: /node_modules/, use: [ { loader: ‘url-loader?limit=12&name=images/[name].[hash:8].[ext]’, }, ], }, { test: /.(woff|woff2|ttf|eot|svg)$/, exclude: /node_modules/,//首席填坑官∙苏南的专栏,公众号:honeyBadger8 use: [ { loader: ‘file-loader?name=fonts/[name].[hash:8].[ext]’, }, ], }, ],},项目中常用loaderbabel-loader、awesome-typescript-loader js/ts编译,css-loader、postcss-loader、sass-loader、less-loader、style-loader 等css样式处理file-loader、url-loader、html-loader等图片/svg/html等的处理,plugins - 插件UglifyJsPluginHotModuleReplacementPluginNoEmitOnErrorsPluginHtmlWebPackPluginExtractTextPluginPreloadWebpackPlugin等等,很多很多,插件的详解会留在下一章节详细介绍,欢迎持续关注。plugins/loader 区别新入门的一些同学可能会有些疑惑,不是有loader了吗?为什么还plugins呢,还要它做什么?loader的作用在于解析文件,比如把ES6转换成es5,甚至ES3,毕竟还有万恶的IE嘛;把Sass、Less解析成CSS,给CSS自动加上兼容的前缀;对图片进行一个解析等等;plugins呢?它在干啥?它在吹水、喝茶、嗑瓜子聊天,当然这是loader在没有把项目做完之前,loader下班时间就是plugins苦难的开始,它要对loader干的事情进行优化分类、提取精华(公共代码提取)、做压缩处理(js/css/html压缩)、输出指定的目录等……,反正也是很苦逼!webpack-dev-server这个有些老生常谈了,新手上路一般都有用它,公司因为现在是结合了 微服务,整套流程是结合:Dockerfile、nodejs、express等一起在线构建编译的,所以大部分项目都不会走webpack-dev-server;我们开发环境就是使用 express + webpack-dev-middleware + webpack-hot-middleware+ ‘…’;contentBase :告诉服务(dev server)在哪里查找文件,默认不指定会在是当期项目根目录,historyApiFallback:可以是boolean、 object,默认响应的入口文件,包括404都会指向这里,object见下面示例:compress:启用 gzip 压缩,publicPath:它其实就是 output.publicPath,当你改变了它,即会覆盖了output的配置,stats: 可以自定控制要显示的编译细节信息,proxy:它其实就是http-proxy-middleware,可以进行处理一些代理的请求。//方式一:不配置方式二的内容 webpack-dev-server –config webpack/webpack.config.dev.js//指定 端口: –port=8080 //开启热更新:–hot//gzip: –compress//方式二devServer : contentBase:’./assets’, host: ‘0.0.0.0’, port: 9089, publicPath: ‘/assets/’, historyApiFallback: { index: ‘/views/index.html’ }, /* 匹配路径,进入不同的入口文件,首席填坑官∙苏南的专栏,公众号:honeyBadger8 rewrites: [ { from: /^/$/, to: ‘/views/landing.html’ }, { from: /^/subpage/, to: ‘/views/subpage.html’ }, { from: /./, to: ‘/views/404.html’ } ] } */ compress: true, noInfo: true, inline: true, hot: true, stats: { colors: true, chunks: false }, proxy:{ ‘/mockApi’: ‘https://easy-mock.com/project/5a0aad39eace86040263d' ,//请求可直接写成 /mockApi/api/login… }}webpack4删除的点:module.loadersNoErrorsPluginCommonsChunkPluginDefinePluginOccurenceOrderPlugin欢迎补充……,平时用到的大概就是这些尾声: 以上就是工作中react自定脚手架的配置总结,希望能对您有所帮助,webpack4的改动蛮大的,功能比之前强大了少,也简便了开发者很多的麻烦,效率大大提高,但同时也意味着我们对于底层的东西,了解的更少了,下一章节将为大家分享一些常用的插件/以及用法的分析,欢迎持续关注,记得点个赞哦,当然您能动动手指关注下方公众号就更棒了,谢谢支持!更多文章:easy-mock 最好的备胎没有之一immutability因React官方出镜之使用总结分享!小程序项目之做完项目老板给我加了6k薪资~小程序项目之填坑小记面试踩过的坑,都在这里了~你应该做的前端性能优化之总结大全!如何给localStorage设置一个过期时间?动画一点点 - 如何用CSS3画出懂你的3D魔方?动画一点点 - 手把手教你如何绘制一辆会跑车SVG Sprites Icon的使用技巧作者:苏南 - 首席填坑官链接:https://blog.csdn.net/weixin…交流:912594095、公众号:honeyBadger8本文原创,著作权归作者所有。商业转载请联系@IT·平头哥联盟获得授权,非商业转载请注明原链接及出处。 ...

November 12, 2018 · 2 min · jiezi

ES6 系列之 Babel 是如何编译 Class 的(下)

前言在上一篇 《 ES6 系列 Babel 是如何编译 Class 的(上)》,我们知道了 Babel 是如何编译 Class 的,这篇我们学习 Babel 是如何用 ES5 实现 Class 的继承。ES5 寄生组合式继承function Parent (name) { this.name = name;}Parent.prototype.getName = function () { console.log(this.name)}function Child (name, age) { Parent.call(this, name); this.age = age;}Child.prototype = Object.create(Parent.prototype);var child1 = new Child(‘kevin’, ‘18’);console.log(child1);原型链示意图为:关于寄生组合式继承我们在 《JavaScript深入之继承的多种方式和优缺点》 中介绍过。引用《JavaScript高级程序设计》中对寄生组合式继承的夸赞就是:这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。ES6 extendClass 通过 extends 关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。以上 ES5 的代码对应到 ES6 就是:class Parent { constructor(name) { this.name = name; }}class Child extends Parent { constructor(name, age) { super(name); // 调用父类的 constructor(name) this.age = age; }}var child1 = new Child(‘kevin’, ‘18’);console.log(child1);值得注意的是:super 关键字表示父类的构造函数,相当于 ES5 的 Parent.call(this)。子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工。如果不调用 super 方法,子类就得不到 this 对象。也正是因为这个原因,在子类的构造函数中,只有调用 super 之后,才可以使用 this 关键字,否则会报错。子类的 __proto__在 ES6 中,父类的静态方法,可以被子类继承。举个例子:class Foo { static classMethod() { return ‘hello’; }}class Bar extends Foo {}Bar.classMethod(); // ‘hello’这是因为 Class 作为构造函数的语法糖,同时有 prototype 属性和 proto 属性,因此同时存在两条继承链。(1)子类的 proto 属性,表示构造函数的继承,总是指向父类。(2)子类 prototype 属性的 proto 属性,表示方法的继承,总是指向父类的 prototype 属性。class Parent {}class Child extends Parent {}console.log(Child.proto === Parent); // trueconsole.log(Child.prototype.proto === Parent.prototype); // trueES6 的原型链示意图为:我们会发现,相比寄生组合式继承,ES6 的 class 多了一个 Object.setPrototypeOf(Child, Parent) 的步骤。继承目标extends 关键字后面可以跟多种类型的值。class B extends A {}上面代码的 A,只要是一个有 prototype 属性的函数,就能被 B 继承。由于函数都有 prototype 属性(除了 Function.prototype 函数),因此 A 可以是任意函数。除了函数之外,A 的值还可以是 null,当 extend null 的时候:class A extends null {}console.log(A.proto === Function.prototype); // trueconsole.log(A.prototype.proto === undefined); // trueBabel 编译那 ES6 的这段代码:class Parent { constructor(name) { this.name = name; }}class Child extends Parent { constructor(name, age) { super(name); // 调用父类的 constructor(name) this.age = age; }}var child1 = new Child(‘kevin’, ‘18’);console.log(child1);Babel 又是如何编译的呢?我们可以在 Babel 官网的 Try it out 中尝试:‘use strict’;function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(“this hasn’t been initialised - super() hasn’t been called”); } return call && (typeof call === “object” || typeof call === “function”) ? call : self;}function _inherits(subClass, superClass) { if (typeof superClass !== “function” && superClass !== null) { throw new TypeError(“Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.proto = superClass;}function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(“Cannot call a class as a function”); }}var Parent = function Parent(name) { _classCallCheck(this, Parent); this.name = name;};var Child = function(_Parent) { _inherits(Child, _Parent); function Child(name, age) { _classCallCheck(this, Child); // 调用父类的 constructor(name) var _this = _possibleConstructorReturn(this, (Child.proto || Object.getPrototypeOf(Child)).call(this, name)); _this.age = age; return _this; } return Child;}(Parent);var child1 = new Child(‘kevin’, ‘18’);console.log(child1);我们可以看到 Babel 创建了 _inherits 函数帮助实现继承,又创建了 _possibleConstructorReturn 函数帮助确定调用父类构造函数的返回值,我们来细致的看一看代码。_inheritsfunction _inherits(subClass, superClass) { // extend 的继承目标必须是函数或者是 null if (typeof superClass !== “function” && superClass !== null) { throw new TypeError(“Super expression must either be null or a function, not " + typeof superClass); } // 类似于 ES5 的寄生组合式继承,使用 Object.create,设置子类 prototype 属性的 proto 属性指向父类的 prototype 属性 subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); // 设置子类的 proto 属性指向父类 if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.proto = superClass;}关于 Object.create(),一般我们用的时候会传入一个参数,其实是支持传入两个参数的,第二个参数表示要添加到新创建对象的属性,注意这里是给新创建的对象即返回值添加属性,而不是在新创建对象的原型对象上添加。举个例子:// 创建一个以另一个空对象为原型,且拥有一个属性 p 的对象const o = Object.create({}, { p: { value: 42 } });console.log(o); // {p: 42}console.log(o.p); // 42再完整一点:const o = Object.create({}, { p: { value: 42, enumerable: false, // 该属性不可写 writable: false, configurable: true }});o.p = 24;console.log(o.p); // 42那么对于这段代码:subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } });作用就是给 subClass.prototype 添加一个可配置可写不可枚举的 constructor 属性,该属性值为 subClass。_possibleConstructorReturn函数里是这样调用的:var _this = _possibleConstructorReturn(this, (Child.proto || Object.getPrototypeOf(Child)).call(this, name));我们简化为:var _this = _possibleConstructorReturn(this, Parent.call(this, name));_possibleConstructorReturn 的源码为:function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError(“this hasn’t been initialised - super() hasn’t been called”); } return call && (typeof call === “object” || typeof call === “function”) ? call : self;}在这里我们判断 Parent.call(this, name) 的返回值的类型,咦?这个值还能有很多类型?对于这样一个 class:class Parent { constructor() { this.xxx = xxx; }}Parent.call(this, name) 的值肯定是 undefined。可是如果我们在 constructor 函数中 return 了呢?比如:class Parent { constructor() { return { name: ‘kevin’ } }}我们可以返回各种类型的值,甚至是 null:class Parent { constructor() { return null }}我们接着看这个判断:call && (typeof call === “object” || typeof call === “function”) ? call : self;注意,这句话的意思并不是判断 call 是否存在,如果存在,就执行 (typeof call === “object” || typeof call === “function”) ? call : self因为 && 的运算符优先级高于 ? :,所以这句话的意思应该是:(call && (typeof call === “object” || typeof call === “function”)) ? call : self;对于 Parent.call(this) 的值,如果是 object 类型或者是 function 类型,就返回 Parent.call(this),如果是 null 或者基本类型的值或者是 undefined,都会返回 self 也就是子类的 this。这也是为什么这个函数被命名为 _possibleConstructorReturn。总结var Child = function(_Parent) { _inherits(Child, _Parent); function Child(name, age) { _classCallCheck(this, Child); // 调用父类的 constructor(name) var _this = _possibleConstructorReturn(this, (Child.proto || Object.getPrototypeOf(Child)).call(this, name)); _this.age = age; return _this; } return Child;}(Parent);最后我们总体看下如何实现继承:首先执行 _inherits(Child, Parent),建立 Child 和 Parent 的原型链关系,即 Object.setPrototypeOf(Child.prototype, Parent.prototype) 和 Object.setPrototypeOf(Child, Parent)。然后调用 Parent.call(this, name),根据 Parent 构造函数的返回值类型确定子类构造函数 this 的初始值 _this。最终,根据子类构造函数,修改 _this 的值,然后返回该值。ES6 系列ES6 系列目录地址:https://github.com/mqyqingfeng/BlogES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。 ...

November 8, 2018 · 4 min · jiezi

ES6 系列之 Babel 是如何编译 Class 的(上)

前言在了解 Babel 是如何编译 class 前,我们先看看 ES6 的 class 和 ES5 的构造函数是如何对应的。毕竟,ES6 的 class 可以看作一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。constructorES6 中:class Person { constructor(name) { this.name = name; } sayHello() { return ‘hello, I am ’ + this.name; }}var kevin = new Person(‘Kevin’);kevin.sayHello(); // hello, I am Kevin对应到 ES5 中就是:function Person(name) { this.name = name;}Person.prototype.sayHello = function () { return ‘hello, I am ’ + this.name;};var kevin = new Person(‘Kevin’);kevin.sayHello(); // hello, I am Kevin我们可以看到 ES5 的构造函数 Person,对应 ES6 的 Person 类的 constructor 方法。值得注意的是:类的内部所有定义的方法,都是不可枚举的(non-enumerable)以上面的例子为例,在 ES6 中:Object.keys(Person.prototype); // []Object.getOwnPropertyNames(Person.prototype); // [“constructor”, “sayHello”]然而在 ES5 中:Object.keys(Person.prototype); // [‘sayHello’]Object.getOwnPropertyNames(Person.prototype); // [“constructor”, “sayHello”]实例属性以前,我们定义实例属性,只能写在类的 constructor 方法里面。比如:class Person { constructor() { this.state = { count: 0 }; }}然而现在有一个提案,对实例属性和静态属性都规定了新的写法,而且 Babel 已经支持。现在我们可以写成:class Person { state = { count: 0 };}对应到 ES5 都是:function Person() { this.state = { count: 0 };}静态方法所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上 static 关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。ES6 中:class Person { static sayHello() { return ‘hello’; }}Person.sayHello() // ‘hello’var kevin = new Person();kevin.sayHello(); // TypeError: kevin.sayHello is not a function对应 ES5:function Person() {}Person.sayHello = function() { return ‘hello’;};Person.sayHello(); // ‘hello’var kevin = new Person();kevin.sayHello(); // TypeError: kevin.sayHello is not a function静态属性静态属性指的是 Class 本身的属性,即 Class.propName,而不是定义在实例对象(this)上的属性。以前,我们添加静态属性只可以这样:class Person {}Person.name = ‘kevin’;因为上面提到的提案,现在可以写成:class Person { static name = ‘kevin’;}对应到 ES5 都是:function Person() {};Person.name = ‘kevin’;new 调用值得注意的是:类必须使用 new 调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用 new 也可以执行。class Person {}Person(); // TypeError: Class constructor Foo cannot be invoked without ’new’getter 和 setter与 ES5 一样,在“类”的内部可以使用 get 和 set 关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。class Person { get name() { return ‘kevin’; } set name(newName) { console.log(’new name 为:’ + newName) }}let person = new Person();person.name = ‘daisy’;// new name 为:daisyconsole.log(person.name);// kevin对应到 ES5 中:function Person(name) {}Person.prototype = { get name() { return ‘kevin’; }, set name(newName) { console.log(’new name 为:’ + newName) }}let person = new Person();person.name = ‘daisy’;// new name 为:daisyconsole.log(person.name);// kevinBabel 编译至此,我们已经知道了有关“类”的方法中,ES6 与 ES5 是如何对应的,实际上 Babel 在编译时并不会直接就转成这种形式,Babel 会自己生成一些辅助函数,帮助实现 ES6 的特性。我们可以在 Babel 官网的 Try it out 页面查看 ES6 的代码编译成什么样子。编译(一)ES6 代码为:class Person { constructor(name) { this.name = name; }}Babel 编译为:“use strict”;function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(“Cannot call a class as a function”); }}var Person = function Person(name) { _classCallCheck(this, Person); this.name = name;};_classCallCheck 的作用是检查 Person 是否是通过 new 的方式调用,在上面,我们也说过,类必须使用 new 调用,否则会报错。当我们使用 var person = Person() 的形式调用的时候,this 指向 window,所以 instance instanceof Constructor 就会为 false,与 ES6 的要求一致。编译(二)ES6 代码为:class Person { // 实例属性 foo = ‘foo’; // 静态属性 static bar = ‘bar’; constructor(name) { this.name = name; }}Babel 编译为:‘use strict’;function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(“Cannot call a class as a function”); }}var Person = function Person(name) { _classCallCheck(this, Person); this.foo = ‘foo’; this.name = name;};Person.bar = ‘bar’;编译(三)ES6 代码为:class Person { constructor(name) { this.name = name; } sayHello() { return ‘hello, I am ’ + this.name; } static onlySayHello() { return ‘hello’ } get name() { return ‘kevin’; } set name(newName) { console.log(’new name 为:’ + newName) }}对应到 ES5 的代码应该是:function Person(name) { this.name = name;}Person.prototype = { sayHello: function () { return ‘hello, I am ’ + this.name; }, get name() { return ‘kevin’; }, set name(newName) { console.log(’new name 为:’ + newName) }}Person.onlySayHello = function () { return ‘hello’};Babel 编译后为:‘use strict’;var _createClass = function() { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (“value” in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function(Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; };}();function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(“Cannot call a class as a function”); }}var Person = function() { function Person(name) { _classCallCheck(this, Person); this.name = name; } _createClass(Person, [{ key: ‘sayHello’, value: function sayHello() { return ‘hello, I am ’ + this.name; } }, { key: ’name’, get: function get() { return ‘kevin’; }, set: function set(newName) { console.log(’new name 为:’ + newName); } }], [{ key: ‘onlySayHello’, value: function onlySayHello() { return ‘hello’; } }]); return Person;}();我们可以看到 Babel 生成了一个 _createClass 辅助函数,该函数传入三个参数,第一个是构造函数,在这个例子中也就是 Person,第二个是要添加到原型上的函数数组,第三个是要添加到构造函数本身的函数数组,也就是所有添加 static 关键字的函数。该函数的作用就是将函数数组中的方法添加到构造函数或者构造函数的原型中,最后返回这个构造函数。在其中,又生成了一个 defineProperties 辅助函数,使用 Object.defineProperty 方法添加属性。默认 enumerable 为 false,configurable 为 true,这个在上面也有强调过,是为了防止 Object.keys() 之类的方法遍历到。然后通过判断 value 是否存在,来判断是否是 getter 和 setter。如果存在 value,就为 descriptor 添加 value 和 writable 属性,如果不存在,就直接使用 get 和 set 属性。写在后面至此,我们已经了解了 Babel 是如何编译一个 Class 的,然而,Class 还有一个重要的特性就是继承,Class 如何继承,Babel 又该如何编译,欢迎期待下一篇《 ES6 系列之 Babel 是如何编译 Class 的(下)》ES6 系列ES6 系列目录地址:https://github.com/mqyqingfeng/BlogES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。 ...

November 5, 2018 · 4 min · jiezi

ES6 系列之 Babel 将 Async 编译成了什么样子

前言本文就是简单介绍下 Async 语法编译后的代码。Asyncconst fetchData = (data) => new Promise((resolve) => setTimeout(resolve, 1000, data + 1))const fetchValue = async function () { var value1 = await fetchData(1); var value2 = await fetchData(value1); var value3 = await fetchData(value2); console.log(value3)};fetchValue();// 大约 3s 后输出 4Babel我们直接在 Babel 官网的 Try it out 粘贴上述代码,然后查看代码编译成什么样子:“use strict”;function _asyncToGenerator(fn) { return function() { var gen = fn.apply(this, arguments); return new Promise(function(resolve, reject) { function step(key, arg) { try { var info = genkey; var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then( function(value) { step(“next”, value); }, function(err) { step(“throw”, err); } ); } } return step(“next”); }); };}var fetchData = function fetchData(data) { return new Promise(function(resolve) { return setTimeout(resolve, 1000, data + 1); });};var fetchValue = (function() { var _ref = _asyncToGenerator( /#PURE/ regeneratorRuntime.mark(function _callee() { var value1, value2, value3; return regeneratorRuntime.wrap( function _callee$(_context) { while (1) { switch ((_context.prev = _context.next)) { case 0: _context.next = 2; return fetchData(1); case 2: value1 = _context.sent; _context.next = 5; return fetchData(value1); case 5: value2 = _context.sent; _context.next = 8; return fetchData(value2); case 8: value3 = _context.sent; console.log(value3); case 10: case “end”: return _context.stop(); } } }, _callee, this ); }) ); return function fetchValue() { return _ref.apply(this, arguments); };})();fetchValue();_asyncToGeneratorregeneratorRuntime 相关的代码我们在 《ES6 系列之 Babel 将 Generator 编译成了什么样子》 中已经介绍过了,这次我们重点来看看 _asyncToGenerator 函数:function _asyncToGenerator(fn) { return function() { var gen = fn.apply(this, arguments); return new Promise(function(resolve, reject) { function step(key, arg) { try { var info = genkey; var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then( function(value) { step(“next”, value); }, function(err) { step(“throw”, err); } ); } } return step(“next”); }); };}以上这段代码主要是用来实现 generator 的自动执行以及返回 Promise。当我们执行 fetchValue() 的时候,执行的其实就是 _asyncToGenerator 返回的这个匿名函数,在匿名函数中,我们执行了var gen = fn.apply(this, arguments);这一步就相当于执行 Generator 函数,举个例子:function* helloWorldGenerator() { yield ‘hello’; yield ‘world’; return ’ending’;}var hw = helloWorldGenerator();var gen = fn.apply(this, arguments) 就相当于 var hw = helloWorldGenerator();,返回的 gen 是一个具有 next()、throw()、return() 方法的对象。然后我们返回了一个 Promise 对象,在 Promise 中,我们执行了 step(“next”),step 函数中会执行:try { var info = genkey; var value = info.value;} catch (error) { reject(error); return;}step(“next”) 就相当于 var info = gen.next(),返回的 info 对象是一个具有 value 和 done 属性的对象:{value: Promise, done: false}接下来又会执行:if (info.done) { resolve(value);} else { return Promise.resolve(value).then( function(value) { step(“next”, value); }, function(err) { step(“throw”, err); } );}value 此时是一个 Promise,Promise.resolve(value) 依然会返回这个 Promise,我们给这个 Promise 添加了一个 then 函数,用于在 Promise 有结果时执行,有结果时又会执行 step(“next”, value),从而使得 Generator 继续执行,直到 info.done 为 true,才会 resolve(value)。不完整但可用的代码(function() { var ContinueSentinel = {}; var mark = function(genFun) { var generator = Object.create({ next: function(arg) { return this._invoke(“next”, arg); } }); genFun.prototype = generator; return genFun; }; function wrap(innerFn, outerFn, self) { var generator = Object.create(outerFn.prototype); var context = { done: false, method: “next”, next: 0, prev: 0, sent: undefined, abrupt: function(type, arg) { var record = {}; record.type = type; record.arg = arg; return this.complete(record); }, complete: function(record, afterLoc) { if (record.type === “return”) { this.rval = this.arg = record.arg; this.method = “return”; this.next = “end”; } return ContinueSentinel; }, stop: function() { this.done = true; return this.rval; } }; generator._invoke = makeInvokeMethod(innerFn, context); return generator; } function makeInvokeMethod(innerFn, context) { var state = “start”; return function invoke(method, arg) { if (state === “completed”) { return { value: undefined, done: true }; } context.method = method; context.arg = arg; while (true) { state = “executing”; if (context.method === “next”) { context.sent = context._sent = context.arg; } var record = { type: “normal”, arg: innerFn.call(self, context) }; if (record.type === “normal”) { state = context.done ? “completed” : “yield”; if (record.arg === ContinueSentinel) { continue; } return { value: record.arg, done: context.done }; } } }; } window.regeneratorRuntime = {}; regeneratorRuntime.wrap = wrap; regeneratorRuntime.mark = mark;})();“use strict”;function _asyncToGenerator(fn) { return function() { var gen = fn.apply(this, arguments); return new Promise(function(resolve, reject) { function step(key, arg) { try { var info = genkey; var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then( function(value) { step(“next”, value); }, function(err) { step(“throw”, err); } ); } } return step(“next”); }); };}var fetchData = function fetchData(data) { return new Promise(function(resolve) { return setTimeout(resolve, 1000, data + 1); });};var fetchValue = (function() { var _ref = _asyncToGenerator( /#PURE/ regeneratorRuntime.mark(function _callee() { var value1, value2, value3; return regeneratorRuntime.wrap( function _callee$(_context) { while (1) { switch ((_context.prev = _context.next)) { case 0: _context.next = 2; return fetchData(1); case 2: value1 = _context.sent; _context.next = 5; return fetchData(value1); case 5: value2 = _context.sent; _context.next = 8; return fetchData(value2); case 8: value3 = _context.sent; console.log(value3); case 10: case “end”: return _context.stop(); } } }, _callee, this ); }) ); return function fetchValue() { return _ref.apply(this, arguments); };})();fetchValue();请原谅我水了一篇文章……ES6 系列ES6 系列目录地址:https://github.com/mqyqingfeng/BlogES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。 ...

October 31, 2018 · 4 min · jiezi

express中使用es6

express官网上给的javascript标准为es5,是不能直接跑es6的,想要在express中使用es6写法,可以用转码器Babel进行转码。开发环境中express项目中安装babel-cli$ npm install –save-dev babel-cli安装presetsnpm install –save-dev babel-preset-es2015 babel-preset-stage-2在package.json里添加运行的脚本{ … “scripts”: { “start”: “babel-node index.js –presets es2015,stage-2” } …}到此就可以使用es6的写法了,写一段es6运行npm start刚开始学习express的时候,会遇到一个问题:每次改一点点代码,都需要重启服务。我们希望能够实现“热更新”的效果,接下来我们就可以使用nodemon监视文件修改,达到热更新效果,而不比每次都重启服务安装nodemonnpm install –save-dev nodemon修改脚本{ … “scripts”: { “start”: “nodemon index.js –exec babel-node –presets es2015,stage-2” } …}运行 npm start现在更改js代码,不需要重启服务,就可以实现效果了

October 17, 2018 · 1 min · jiezi

优秀前端必知的话题:我们应该做些力所能及的优化

在 Web 应用开发过程中,我们经常谈及到的就是优化,而优化往往又是既简单而又复杂的过程,优化这个命题很广,最终体现出来的都是用户体验问题,我们一切优化都是为了用户体验。为什么说简单?在现代 Web 开发生态中,有非常优秀的工具链帮助我们做一些很实际的优化工作,例如 webpack 。这些工具可以很好的帮助我们解决包之间的依赖、减小包大小、提取公共模块等等问题。为什么说复杂?优化这个话题我们谈了很多年,只要有用户群,优化问题就会一直存在。而优化工作涉及的领域特别广,包含的因素又特别多,有时候需要针对特定的场景做特殊的优化工作,所以说又很复杂。不管是简单还是复杂,作为程序员,我们应当做一些我们力所能及的优化工作,本文属于探讨性话题,希望广大网友能够在留言区留下您的一些思考。Code Splitting这里不探讨如何书写高性能的代码,而是探讨下我们书写的代码该如何被构建。这里以 webpack 为构建工具(版本为4.19),来阐述下在 webpack 我们该做的优化工作。webpack 从 v4 版本开始,做了很多的优化工作,详情请看这里 。我们就拿 Code Splitting 说起,Code Splitting 是 webpack 一项重要的编译特性,能够帮助我们将代码进行拆包,抽取出公共代码。利用这项特性我们可以做更多的优化工作,减少加载时间,例如可以做按需加载。而在 webpack 中开启 Code Splitting 也很简单,它是一项开箱即用的插件,示例代码如下:module.export = {// …optimization: {splitChunks: {chunks: ‘all’}}}上面的 chunks 配置建议大家配置为 all ,详细配置请参考:splitChunks.chunks这里给出个参考结果:分别为配置前和配置后这里明显多出了几个包含 vendors~ 字样的文件,而且你会发现 app 这个文件被提取出了 vendorsappreact_vendor 和 vendors_app_redux_vendor 这两个文件。至于最大的文件为什么还有1.02M,我们可以使用 analyze 来分析下包的结构,这里是由于包含了 antd 的文件。在实际开发过程中,路由也是需要进行 Code Splitting ,在过去我们经常使用 bundle-loader ,来帮助我们进行代码分割,它是基于 require.ensure 接口进行实现。既然我们可以对路由进行代码分割,那么路由页面中的组件我们是否可以按需加载,实现代码分割呢?答案是显然的。这种业务场景也是非常的多,这里我举一个例子,就是一个登录页面,登录有多种方式,其中最常见的就是账号登录和扫码登录,默认为扫码登录。当用户没有选择账号登录,那么按道理这部分代码我们可以不进行加载,从而减少加载时间,优化用户体验。我们建议能进行组件级分割就分割,最大化减小页面大小。在 React 中虽然也可以使用 bundle-loader 来实现组件级代码分割,但是也会有一些问题。在后来,React Router 官方也推荐使用 react-loadable 来进行代码分割。强烈建议 React 使用者使用此库,该库的功能很强大,是基于 import() 实现。它可以实现预加载、重新加载等等强大功能。Tree Shaking如果你对自己编写的代码很了解,你可以通过在 package.json 中添加 sideEffects 来启用 Tree Shaking ,即摇树优化,帮助我们删掉一些不用的代码。这里不再赘述,详情可以点击Tree Shaking。Dynamic import在谈到 Code Spliting 时,我们不得不想到 dynamic import ,在之前版本的 webpack 中,我们想实现动态加载使用的是 require.ensure ,而在新版本中,取而代之的 import() ,这是TC39关于使用 import()的提案,而目前 import()兼容性如下:import() 返回一个 Promise ,如果你想使用它请确保支持 Promise 或者使用 Polyfill ,在想使用 import() 前,我们还得使用预处理器,我们可以使用 @babel/plugin-syntax-dynamic-import 插件来帮助webpack解析。webpack 官方给了我们一个 dynamic import 的示例 ,这里我就不做举例。使用 import() 我们可以很方便的实现 preload 预加载、懒加载以及上面谈到的 Code Splitting。PolyfillPolyfill 现在对于大家来说应该并不陌生,他可以帮助我们使用一些浏览器目前并不支持的特性,例如 Promise 。在Babel中,官方建议使用 babel-preset-env 配合 .browserslistrc ,开发人员可以无需关心目标环境,提升开发体验。尤其在 Polyfill 方面,只要我们配置好 .browserslistrc ,Babel 就可以智能的根据我们配置的浏览器列表来帮助我们自注入 Polyfill ,比如:.babelrc{“presets”: [["@babel/preset-env",{“useBuiltIns”: “entry”}]]}useBuiltIns 告诉 babel-preset-env 如何配置 Polyfill ,这里我配置为:entry ,然后在 webpack 入口文件中引入 import ‘@babel/polyfill’ 即可,这里注意不能多次引入 import ‘@babel/polyfill’ ,否则会报错。.browserslistrc> 1%Last 2 versions这样就完成了自动根据 .browserslistrc注入 Polyfill ,但是这样有一个问题,就是所有的浏览器都会有 Polyfill 的并集。每个浏览器之间的特性具有很大的差异,为了尽可能的减小包的大小,我们可以为每个主流浏览器单独生成 Polyfill ,不同的浏览器加载不同的 Polyfill 。首屏文件SPA 程序打包出来的html文件一般都是很小的,也就2kb左右,似乎我们还可以利用下这个大小做个优化,有了解初始拥塞窗口 的同学应该知道,通常是14.6KB,也就意味着这我们还能利用剩下的12KB左右的大小去干点什么,这了我建议内联一些首屏关键的css文件(可以使用 criticalCSS ),或者将css初始化文件内联进去,当然你也可以放其他东西,这里只是充分利用下初始拥塞窗口 特性。这里顺便讲下css初始化,css初始化有很多种选择,其中有三种比较出名的,分别是:normalize.css 、sanitize.css 和 reset.css 。关于这三种的区别我就直接引用了。normalize.css and sanitize.css correct browser bugs while carefully testing and documenting changes. normalize.css styles adhere to css specifications. sanitize.css styles adhere to common developer expectations and preferences. reset.css unstyles all elements. Both sanitize.css and normalize.css are maintained in sync.缓存在利用 webpack 打包完之后,我们有些文件几乎不会变更,比如我这里列举的react、redux、polyfill相关的文件。entry: {react_vendor: [‘react’, ‘react-dom’, ‘react-router-dom’],redux_vendor: [‘react-redux’,‘redux’, ‘redux-immutable’,‘redux-saga’, ‘immutable’],polyfill: ‘@babel/polyfill’,app: path.join(process.cwd(),‘app/app.js’)}这些不变的文件我们就可以好好的利用下,常见(http 1.1)的就是设置 Etag ,Last-Modified 和 Cache-Control 。前面两种属于对比缓存,还是需要和服务器通信一次,只有当服务器返回 304 ,浏览器才会去读取缓存文件。而 Cache-Control 属于强制缓存,服务器设定 max-age 当过了设定的时间后才会向服务器发起请求。这里打包再配上 chunk-hash 几乎可以完美的配置缓存。当然还可以利用 localStorage 来做缓存,这里提出一种思路,是我以前在效仿百度首页缓存机制想的。我们可以在把js文件版本号弄成一个配置,同时存储在服务端和客户端,比如:{“react_version”: 16.4,“redux_version”: 5.0.6,“web_version”: 1.0}客户端将该版本号存储在 cookie 或其他存储引擎中,这里推荐 localForage 来做存储。服务端将最新版本号渲染到html文件中,然后通过js脚本对比版本号,如若版本号不同,则进行加载对应的js文件,加载成功后再存储到本地存储中。如果相同,则直接取本地存储文件。还有一种缓存的场景,就是有一些api服务端更新的进度很慢,比如一天之内访问的数据都是一样的,这样就可以对客户端进行请求缓存并拦截请求,从而优化速度,减小服务器压力。其他还有其他很多可以优化的地方,比如减少http请求、图片懒加载等等,就不一一列举了,大家可以看雅虎34条军规:尽量减少 HTTP 请求个数——须权衡使用 CDN(内容分发网络)为文件头指定 Expires 或 Cache-Control ,使内容具有缓存性。避免空的 src 和 href使用 gzip 压缩内容把 CSS 放到顶部把 JS 放到底部避免使用 CSS 表达式将 CSS 和 JS 放到外部文件中减少 DNS 查找次数精简 CSS 和 JS避免跳转剔除重复的 JS 和 CSS配置 ETags使 AJAX 可缓存尽早刷新输出缓冲使用 GET 来完成 AJAX 请求延迟加载预加载减少 DOM 元素个数根据域名划分页面内容尽量减少 iframe 的个数避免 404减少 Cookie 的大小使用无 cookie 的域减少 DOM 访问开发智能事件处理程序用 <link> 代替 @import避免使用滤镜优化图像优化 CSS Spirite不要在 HTML 中缩放图像——须权衡favicon.ico要小而且可缓存保持单个内容小于25K打包组件成复合文本写在最后关于优化的文章网上太多太多,这篇文章并不是告诉大家如何优化,而是在平时写代码时能够培养一种习惯、一种意识,就是做我们力所能及的优化以及要知其所以然。文 / GoDotDotDotLESS is MORE编 / 荧声本文已由作者授权发布,版权属于创宇前端。欢迎注明出处转载本文。本文链接:欢迎点赞、收藏、留言评论、转发分享和打赏支持我们。打赏将被完全转交给文章作者。感谢您的阅读。 ...

September 30, 2018 · 2 min · jiezi