代码地址
Webpack 是当下最热门的前端资源模块化管理和打包工具。它可以将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源。还可以将按需加载的模块进行代码分隔,等到实际需要的时候再异步加载。通过 loader 的转换,任何形式的资源都可以视作模块
webpack就是识别你的 入口文件。识别你的模块依赖,来打包你的代码。 webpack做的就是分析代码,转换代码,编译代码,输出代码。 webpack中每个模块有一个唯一的id,是从0开始递增的。整个打包后的bundle.js是一个匿名函数自执行。参数则为一个数组。数组的每一项都为个function。function的内容则为每个模块的内容,并按照require的顺序排列。
如何实现一个简单的webpack 读取文件分析模块依赖 对模块进行解析执行(深度遍历) 针对不同的模块使用相应的loader 编译模块,生成抽象语法树AST。 循环遍历AST树,拼接输出js。此时用浏览器打开index.html可以看到index.js输出this is index.js
使用插件html-webpack-plugin,可以将生成的js自动引入html页面,不用手动添加
安装 npm install html-webpack-plugin --save-dev 配置 plugins: [// 对应的插件 new HtmlWebpackPlugin({ //配置 filename: 'index.html',//输出文件名 template: './index.html',//以当前目录下的index.html文件为模板生成dist/index.html文件 }), ]为了在每次打包后删除之前的dist文件夹,可以用插件clean-webpack-plugin
安装 npm install clean-webpack-plugin --save-dev 配置 const CleanWebpackPlugin = require('clean-webpack-plugin'); plugins: [ new CleanWebpackPlugin(), //传入数组,指定要删除的目录 ]1、 抽离CSS文件
安装 npm install mini-css-extract-plugin --save 配置 const MiniCssExtractPlugin = require("mini-css-extract-plugin"); test: /.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"], new MiniCssExtractPlugin({ filename: "[name].css", }),注意: 此时已经不用再使用style.loader
2、 自动添加前缀
安装 npm i postcss-loader autoprefixer -D 配置创建.browserslistrc文件
last 2 versions > 1% iOS 7 last 3 iOS versions创建postcss.config.css
module.exports = { plugins: [require("autoprefixer")], };webpack.config.js配置
{ test: /.css$/, use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"], },3、Sass和Less 以下以Sass为例,Less类似操作
安装 npm install --save-dev node-sass sass-loader 配置 { test: /.css$/, use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader", "postcss-loader"], },PS: babel-core不能安装太高版本,可以安装7.1.5
配置 创建.babelrc { "presets": ["env"] }webpack.config.js配置
{ test: /.js$/, exclude: /node_modules/, use: ["babel-loader"], },官方文档
上面代码中的path是node中的一个模块,主要用来文件路径Node.js 中,__dirname 总是指向被执行 js 文件的绝对路径,所以当你在 /d1/d2/myscript.js 文件中写了 __dirname, 它的值就是 /d1/d2 。 相反,./ 会返回你执行 node 命令的路径,例如你的工作路径。
假设有如下目录结构
/dir1 /dir2 pathtest.js然后在 pathtest.js 中,有如下代码
var path = require("path"); console.log(". = %s", path.resolve(".")); console.log("__dirname = %s", path.resolve(__dirname));然后执行了下面命令
cd /dir1/dir2 node pathtest.js. 是你的当前工作目录,在这个例子中就是 /dir1/dir2 ,__dirname 是 pathtest.js 的文件路径,在这个例子中就是 /dir1/dir2 。
然而,如果我们的工作目录是 /dir1
cd /dir1 node dir2/pathtest.js将会得到
. = /dir1 __dirname = /dir1/dir2在 require 中使用 . 如果在 dir2/pathtest.js 中调用了 require 方法,去引入位于 dir1 目录的 js 文件,你需要写成
require('../thefile')因为 require 中的路径总是相对于包含它的文件,跟你的工作目录没有关系。
1、解析 解析步骤接收代码并输出 AST。 这个步骤分为两个阶段:词法分析(Lexical Analysis) 和 语法分析(Syntactic Analysis)。 词法分析 词法分析阶段把字符串形式的代码转换为 令牌(tokens) 流。 你可以把令牌看作是一个扁平的语法片段数组:
n * n; [ { type: { ... }, value: "n", start: 0, end: 1, loc: { ... } }, { type: { ... }, value: "*", start: 2, end: 3, loc: { ... } }, { type: { ... }, value: "n", start: 4, end: 5, loc: { ... } }, ... ]每一个 type 有一组属性来描述该令牌:
{ type: { label: 'name', keyword: undefined, beforeExpr: false, startsExpr: true, rightAssociative: false, isLoop: false, isAssign: false, prefix: false, postfix: false, binop: null, updateContext: null },和 AST 节点一样它们也有 start,end,loc 属性。 语法分析 语法分析阶段会把一个令牌流转换成 AST 的形式。 这个阶段会使用令牌中的信息把它们转换成一个 AST 的表述结构,这样更易于后续的操作。 2、转换 转换步骤接收 AST 并对其进行遍历,在此过程中对节点进行添加、更新及移除等操作。 这是 Babel 或是其他编译器中最复杂的过程。 Babel提供了@babel/traverse(遍历)方法维护这AST树的整体状态,并且可完成对其的替换,删除或者增加节点,这个方法的参数为原始AST和自定义的转换规则,返回结果为转换后的AST。 3.生成 代码生成步骤把最终(经过一系列转换之后)的 AST 转换成字符串形式的代码,同时还会创建源码映射(source maps)。 代码生成其实很简单:深度优先遍历整个 AST,然后构建可以表示转换后代码的字符串。 Babel使用 @babel/generator 将修改后的 AST 转换成代码,生成过程可以对是否压缩以及是否删除注释等进行配置,并且支持 sourceMap。
实践链接
babel-polyfill 是为了模拟一个完整的ES2015+环境,旨在用于应用程序而不是库/工具。并且使用babel-node时,这个polyfill会自动加载。这里要注意的是babel-polyfill是一次性引入你的项目中的,并且同项目代码一起编译到生产环境。而且会污染全局变量。像Map,Array.prototype.find这些就存在于全局空间中。
npm install babel-polyfill --savewebpack.config.js配置
entry: { main: ['babel-polyfill','./index.js'] }babel-runtime不会污染全局空间和内置对象原型。事实上babel-runtime是一个模块,你可以把它作为依赖来达成ES2015的支持。 比如环境不支持Promise,你可以在项目中加入
require(‘babel-runtime/core-js/promise’)这样我们就弥补了babel-polyfill的缺点,达到了按需加载的效果。但是在实际项目开发过程中,我们往往会写很多新的es6 api,每次都要手动引入相应的包比较麻烦,维护起来也不方便,每个文件重复引入也造成代码的臃肿。 要解决这个问题,就要用到 babel-plugin-transform-runtime,它会分析我们的 ast 中,是否有引用 babel-rumtime 中的垫片(通过映射关系),如果有,就会在当前模块顶部插入我们需要的垫片。
npm install --save babel-runtime npm install --save-dev babel-plugin-transform-runtime下面在.babelrc中加入以下配置
{ "plugins": ["transform-runtime"] }babel-runtime有个缺点,它不模拟实例方法,即内置对象原型上的方法,所以类似Array.prototype.find,你通过babel-runtime是无法使用的,这只能通过 babel-polyfill 来转码
babel-preset-env 能根据当前的运行环境,自动确定你需要的 plugins 和 polyfills。 修改一下.babelrc的配置
{ "presets": [ ["env", { "targets": { "chrome": 52, "browsers": ["last 2 versions", "safari 7"] }, "modules": false, "useBuiltIns": "usage", "debug": false }] ] }preset-env.targets
"targets" : "last 2 versions, not dead" "targets" : { "chrome": "58", "ie": "11" } "targets" : { "browsers": "last 2 versions", "esmodules": true, // 指定该选项,将会忽略browserslist, //仅支持那些那些原生支持es6 module的浏览器 "safari": true , // 启用safari前沿技术 "node": "true" || "current" //兼容当前node版本代码 }browserslist
last 2 vsersions not deadpreset-env.modules
"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false, defaults to "auto"指定将es6 modules 转换为何种模块规范。一般在webpack 项目中,我们会将此参数设置为false,既将module交由webpack处理,而不是babel。
preset-env.userBuiltIns
"usage" | "entry" | false, defaults to false"entry": 在入口文件中加入所有的内置类型 如果在.babelrc中指定useBuiltIns: 'entry', 则应该在项目代码的顶部引入babel-polyfill
import "@babel/polyfill""usage": 只在当前文件中加入该文件用到的内置类型的polyfill。 设置为usage 不需要在顶部引入polyfill "false": 不自动加入内置类型的polyfill。