We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
模块化:对代码按照功能的不同去划分不同的模块
早期:
构成:模块化标准 + 模块加载器
编译后的文件名如果是vue.runtime.js,模块化采用的是CommonJS
vue.runtime.js
require([./module1], function(module1){ module1.start() })
define(function (require, exports, module){ var $ = require('jquery') module.exports = function () { console.log('hi') $('body').append('<p>hi</p>') } })
ES Modules + CommonJS
Node.js是commonJS规范的主要实践者,它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global。实际使用时,用module.exports定义当前模块对外输出的接口(不推荐直接用exports),用require加载模块。
module
exports
require
global
module.exports
通过script添加 type = "module" 的属性,就可以以 ES Module 的标准执行其中的JS代码。使用:
type = "module"
<script type = "module"> console.log('hi') </script>
serve .//npm工具去启动
ESM自动采用严格模式,忽略'use strict'
'use strict'
每个ES Module都是运行在单独的私有作用域中
ESM是通过 CORS 的方式请求外部 JS 模块的
ESM 的 script标签会延迟执行脚本
export / import
//./module.js const foo = 'es modules' export {foo as fooName}//重命名 export {foo as default} //./app.js import { fooName } from './module.js' import { defaul tas fooName } from './module.js'//必须要指定变量名 console.log(fooName)
导出不是字面量
如果要导出一个字面量:
export default {name,age}
暴露出来的是一个引用关系,只读
导入一些不需要外界控制的子模块
import {} from './module.js' import './module.js'
把所有导出成员都导出来
import * from './module.js'
动态导入模块机制:import...from...只能出现在最顶层
import('./module.js')//返回的是一个promise,promise是异步加载 .then(function(module){ console.log('hi') })
提取模块中默认成员写法
import title from './module.js' //等价于import {default as title} from './module.js'
导出导入成员:如果将import改成export,那么这些成员将作为目前模块的导出成员看待;同时,目前模块也将无法访问到该导出成员
//import { foo } from './module.js' export { foo } from './module.js'
实例:当需要导入的模块过多时,可以新建一个中转文件index.js,把组件导入再导出;default参数必须要进行重命名
//./index.js export { Button } from './button.js' export { Avatar } from './avator.js' export { default as newB } from './button.js'
IE不兼容ESM标签:引用browser-es-module-loader去使用ESM
引入脚本文件到ESM中:cdn服务 unpkg.com/browser-es-module-loader 拿到js文件
<script nomodule src="https://unpkg.com/[email protected]/dist/polyfill.min.js"></script> <script nomodule src="https://unpkg.com/[email protected]/dist/babel-browser-build.js"></script> <script nomodule src="https://unpkg.com/[email protected]/dist/browser-es-module-loader.js"></script>
ESM运行原理:
使用:
1. .js => .mjs 2. node --experimental-modules index.js
不支持第三方模块导出默认成员,因为第三方模块并没有向外去暴露出一个成员/第三方模块都是导出默认成员
import { camelCase } from 'lodash' //错误的写法
与CommonJS交互
ESM可以导入CJS模块
CJS不能导入ESM模块
CJS始终只会导出一个默认成员
import { foo } from './common.js' //foo无法提取
注意:import不是解构导出对象(只是一个固定的用法
与CommonJS模块的差异
//cjs //加载模块函数 console.log(require) //模块对象 console.log(module) //导出对象别名 console.log(exports) //当前文件的绝对路径 console.log(__filename) //当前文件所在目录 console.log(__dirname)
//package.json { "type":"module" }
xx.mjs => xx.js common.js => common.cjs
几种模块化对生产环境产生的影响:
功能:
模块打包器(module bundler):将零散的代码打包到JS文件中
模块打包器(module bundler)
模块加载器(loader):将兼容有问题的代码编译转换
模块加载器(loader)
代码拆分(code splitting):挑选有需要的代码打包。当实际中需要到某个模块,再通过异步去加载它,实现增量加载或者渐进式加载
代码拆分(code splitting)
资源模块(asset module):webpack支持以模块化载入任意的资源文件,例如,JavaScript中可以支持import CSS文件
资源模块(asset module)
模块化工具的作用:打包工具解决的是前端整体的模块化,并不单指JavaScript模块化
yarn init --yes yarn add webpack webpack-cil --dev //引入webpack模块 yarn webpack //webpack从index.js开始打包 //打包结果会存放在dist目录里 //可以把webpack命令放在package.json中简化打包过程 "scripts":{ "build":"webpack" } yarn build
约定:入口文件src/index.js -> dist/main.js
src/index.js
dist/main.js
自定义:src/main.js
src/main.js
在根目录下添加webpack.config.js
const path = require('path') module.exports = { entry:'./src/main.js', output:{ filename:'bundle.js', //path:'output' path:path.join(__dirname,'output') //指定输出文件所在的目录 //都需要使用绝对路径 } }
Node.js的__dirname,__filename,process.cwd(),./的一些坑 jawil/blog#18 __dirname: 总是返回被执行的 js 所在文件夹的绝对路径 __filename: 总是返回被执行的 js 的绝对路径 process.cwd(): 总是返回运行 node 命令时所在的文件夹的绝对路径 ./: 跟 process.cwd() 一样,返回 node 命令时所在的文件夹的绝对路径
Node.js的__dirname,__filename,process.cwd(),./的一些坑 jawil/blog#18
yarn webpack //默认使用prouction模式工作:webpack会启动一些优化插件,自动压缩代码 yarn webpack --mode development //添加一些调试过程的辅助到代码当中 yarn webpack --mode none //原始状态打包
也可以在配置中添加此属性
//webpack.config.js const path = require('path') module.exports = { mode:'development', ... }
模块私有作用域
默认不解析css文件,需要用适当的加载器loader去处理此类型资源文件,使得css文件转换成一个js模块
安装css-loader
yarn add css-loader --dev yarn add style-loader --dev//将转换的js结果通过style标签追加到页面上
添加加载资源规则配置
//webpack.config.js module: { rules:[ { test:/.css$/, use:[ 'style-loader', 'css-loader' //从后往前执行 ] } ] }
打包入口 ≈ 运行入口
JavaScript驱动整个前端应用的业务
webpack建议在js引入css文件,原因:
根据代码的需要动态导入资源
需要资源的不是应用,而是代码
import './xxx.css'
意义:
示例:
import icon from './icon.png' const img = new Image() img.src = icon document.body.append(img)//到body中
//webpack.config.js module: { rules:[ ... { test:/.png$/, use:'file-loader' } ] }
yarn webpack
//webpack.config.js output:{ ... publicPath:'dist/' }
Data URLs:以代码的形式直接表示一个文件
不需要独立的物理文件了
合适打包体积小的资源,体积过大打包文件就过大,运行时间长
安装url-loader
yarn add url-loader --dev
配置
//webpack.config.js { test:'/.png$/', use:{ loader:'url-loader', options:{ limit: 10 * 1024 //只处理10kb以下的文件!还是要安装file-loader模块 } } }
优化: 小文件使用Data URLs,减少请求次数 大文件单独提取,提高加载速度
优化:
小文件使用Data URLs,减少请求次数
大文件单独提取,提高加载速度
编译转换类
文件操作类
代码检查类
!webpack不会自动编译ES6,因为模块打包需要,才会处理import和export webpack只是打包工具 加载器可以用来编译转换代码
!webpack不会自动编译ES6,因为模块打包需要,才会处理import和export
使用babel-loader编译代码
yarn add babel-loader @babel/core @babel/preset-env --dev
//webpack.config.js rules: [ { test: /.js$/, use: { loader:'babel-loader', options:{ presets:['@babel/preset-env'] //env插件集合,包括了全部的ES特性 } } } ]
webpack兼容多种模块化标准:
遵循ES Modules标准的import声明
遵循CommonJS标准的require函数
const createHeading = require('./xx.js').default
遵循AMD标准的define函数和require函数
define(['./heading.js','./main.css'], (createHeading, icon) => { ... }) require(['./heading.js','./main.css'], (createHeading, icon) => { ... })
@import指令
url函数
所有需要引用资源的地方都会被webpack找出来,根据不同的配置交给不同的loader处理,最后将处理的结果整体打包到输出目录 webpack就这样完成项目的模块化
所有需要引用资源的地方都会被webpack找出来,根据不同的配置交给不同的loader处理,最后将处理的结果整体打包到输出目录
webpack就这样完成项目的模块化
依赖树
webpack递归此依赖树,找到对应节点资源文件,根据rule使用加载器加载这个模块,最后的结果放到bundle.js中
loader机制是webpack的核心
示例:css-loader → style-loader
loader专注实现资源模块加载
plugin解决其他自动化工作:清除dist目录、拷贝静态文件至输出目录、压缩输出代码
yarn add clean-webpack-plugin --dev
//webpack.config.js const { CleanWebpackPlugin } = require('clean-webpack-plugin') plugin: [ new CleanWebpackPlugin() ]
每次执行前就可以清除dist目录的文件了
根目录里有index.html文件,每次部署都需要上次dist文件和index.html,我们可以直接使用webpack打包html文件,删除根目录文件 使用插件 自定义输出内容 同时输出多个页面文件
根目录里有index.html文件,每次部署都需要上次dist文件和index.html,我们可以直接使用webpack打包html文件,删除根目录文件
自动生成使用bundle.js的html
yarn add html-webpack-plugin --dev
//webpack.config.js const htmlWebpackPlugin = require('html-webpack-plugin') //不需要解构 output: { filename:'bundle.js', path: path.join(__dirname, 'dist') //publicPath: 'dist/' } plugin: [ //用于生成index.html new htmlWebpackPlugin({ title:'Webpack Plugin Sample', //html标题 meta:{ viewport:'width=device-width' //对象的形式设置页面元素标签 } /** * 自定义输出内容 */ }), //用于生成about.html new HtmlWebpackPlugin({ filename:'about.html', }) ]
最后会输出一个index.html文件到打包目录
将public里的文件完整拷贝到输出目录
yarn add copy-webpack-plugin --dev
//webpack.config.js const copyWebpackPlugin = require('copy-webpack-plugin') //不需要解构 plugin: [ ... new copyWebpackPlugin([ 'public' ]) ]
loader只加载模块,plugin拥有更宽的能力范围
plugin通过钩子机制实现
钩子机制
钩子类似于web中的事件,为了便于插件扩展,webpack给每一个环节都埋下了钩子Honk。开发插件时,往不同的节点挂载不同的任务,就可以扩展webpack的能力
webpack要求插件必须是一个函数或者一个包含apply方法的对象
apply方法
////webpack.config.js class MyPlugin { /** * compiler对象包含了此次所有的配置信息 */ apply(compiler) { console.log('MyPlugin启动时调用') //名称1 + 挂载到钩子的函数2 compiler.hooks.emit.tap('MyPlugin', compilation => { //compilation此次打包的上下文 for (const name in compilation.assets){ /** * name 文件名 * compilation.assets[name].source() 文件内容 */ if(name.endsWith('.js')){ //判断文件名是否以.js结尾 const contents = compilation.assets[name].source() const withoutComments = contents.replace(/\/\*\*+\*\//g,'')//全局替换注释 } compilation.assets[name] = { source: () => withoutComments, size: () => withoutComments.length } } }) } } //-----使用------ plugins:[ new MyPlugin() ]
eg:清除bundle.js中不必要的注释
借用emit()钩子
实现:通过在生命周期的钩子中挂载函数实现扩展
watch工作模式:监听文件变化,自动重新打包
watch
yarn webpack --watch
BrowserSync自动刷新功能
BrowserSync
browser-sync dist --files "**/*"
缺:麻烦、效率低
Webpack Dev Server提供用于开发的HTTP Server:集成自动编译和自动刷新浏览器
Webpack Dev Server
yarn add webpack-dev-server
特点:为了工作效率没有将结果打包到磁盘中,减少磁盘的读写操作
静态文件:开发阶段不需要参与webpack构建,但同样需要被served
默认只会serve打包输出文件
只要是webpack输出的文件,都可以直接被访问
其它资源文件也需要serve
//webpack.config.js module.exports = { ... devServer: { contentBase: './public' //可以额外为开发服务器指定查找资源目录 } } plugins: [ //开发阶段最好不要使用这个插件:静态文件拷贝 //new CopyWebpackPlugin(['public']) ]
解决开发环境接口跨域请求问题
域名协议端口不一致
解决方式1:跨域资源共享(CORS),使用的CORS的前提是API必须支持,并不是任何情况下API都应该支持。如果同源部署就没有必要使用CORS
跨域资源共享(CORS)
解决方式2:Webpack Dev Server支持配置代理
目标:将github API代理到开发服务器
Endpoint可以理解为接口端点/入口
devServer: { proxy:{ '/api':{ //http://localhost:8080/api/users 相当于请求 http://api.github.com/api/users target:'http://api.github.com',//代理目标 //http://localhost:8080/api/users 相当于请求 http://api.github.com/users pathRewrite: { '^/api':'' }, //不能使用localhost:8080作为请求github的主机名 changeOrigin: true } } }
主机名是http协议中的相关概念
解决:运行代码与源代码之间完全不同,无法调试应用,错误信息无法定位。调试和报错都是基于运行代码
Source Map是一个独立的map文件,与源码在同一个目录下
Source Map
JavaScript Source Map 详解 Source map就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置
JavaScript Source Map 详解
Source map就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置
运行代码通过Source Map逆向解析得到源代码
例子:新版jquery.min.js文件手动添加注释
//# sourceMappingURL = jquery-3.4.1.min.map
Source Map解决了源代码与运行代码不一致所产生的我问题
基本使用:
//webpack.config.js devtool:'source-map'
bundle.js文件最后也自动加上注释
yarn webpack sever dist
eval是js中的一个函数 控制台中的eval运行在虚拟机中,可以使用sourcelURL声明这段代码所属的文件路径
eval是js中的一个函数
控制台中的eval运行在虚拟机中,可以使用sourcelURL声明这段代码所属的文件路径
sourcelURL
设置成eval模式:
//webpack.config.js devtool:'eval'
eval:只能定位哪一个文件除了错误
eval
eval-source-map:同样使用eval模式执行代码,可以定位到行和列的信息。相比eval生成了source-map
eval-source-map
cheap-eval-source-map:只能定位到行信息,快一点,显示ES6转换后的代码
cheap-eval-source-map
cheap-module-eval-source-map:也定位到行,但显示的是我们写的源代码没有经过loader加工
cheap-module-eval-source-map
inline-source-map:普遍的source-map文件都是以物理文件存在,而该模式是使用dataURL的方式嵌入到代码中。代码体积大很多,不常用
inline-source-map
hidden-source-map:构建当中生成了该文件但不展示
hidden-source-map
nosources-source-map:没有源代码但是提供了行列信息。以上两者保护生产环境代码不被暴露
nosources-source-map
eval是否使用eval执行模块代码 cheap-source-map是否包含行信息 module是否能够得到loader处理之前的源代码
eval是否使用eval执行模块代码
cheap-source-map是否包含行信息
cheap-source-map
module是否能够得到loader处理之前的源代码
开发模式:cheap-module-eval-source-map
生产模式:不生成source-map
调式阶段:nosources-source-map,定位到源代码位置,不至于向外暴露你源代码的内容
自动刷新:不丢失当前页面状态
自动刷新
HMR集成在webpack-dev-server中
webpack-dev-server
webpack-dev-server --hot
或者在配置文件中开启
//webpack.config.js const webpack = require('webpack') const HtmlWebpack = require('html-webpack-plugin') //载入webpack内置插件 devServer: { hot: true } plugin: [ new webpack.HotModuleReplacementPlugin() ]
yarn webpack-dev-server --open
//main.js //如果对这个模块进行处理了就不会自动刷新 module.hot.accept('./editor', () => { //editor更新需要在这里手动处理热替换逻辑 })
eg:针对editor模块处理热替换
//main.js let lastEditor = editor module.hot.accept('./editor', () => { const value = lastEditor.innerHTML //先保存状态 document.body.removeChild(editor) //移除元素 const newEditor = createEditor()//创建一个新的元素追加到页面当中 newEditor.innerHTML = value //再设置新元素状态 document.body.appendChild(newEditor) })
//main.js //注册这个图片的热替换处理函数 const img = new Image() module.hot.accept('./better.png', () => { img.src = background //设置为新的src就可以了 })
处理HMR的代码报错会导致自动刷新:使用hotOnly
hotOnly
没启用HMR的情况下,HMR API报错
因为没有module.hot这个对象
module.hot
if(module.hot){ ...业务代码 }
代码中多了一些与业务无关的代码,但不会影响生产环境
生产环境注重运行效率——模式(mode)
模式(mode)
为不同的工作环境创建不同的配置:
配置文件根据环境不同导出不同配置
webpack支持导出一个函数,这个函数返回一个配置对象
//webpack.config.js //env-通过cli传递的环境名参数,argv-运行cli传递的所有参数 module.exports = (env, argv) => { const config = { //开发模式 mode:'development', ... } if(env === 'production'){ config.mode = 'production' config.devtool = false//禁用掉source map config.plugins = [ ...config.plugin, new CleanWebpacPlugin() new CopyWebpackplugin(['public']) //开发阶段的插件,只有在上限打包之前才有自己的价值 return config ] } }
yarn webpack --env production
不同环境对应不同配置文件
公共配置:
//webpack.common.js //直接把webpack.dev.js复制过来
生产环境配置:
//webpack.prod.js const common = require('./webpack.common') //导入公共配置 const merge = require('webpack-merge') const { CleanWebpackPlugin } = require('clean-webpack-plugin') const CopyWebpackPlugin = require('copy-webpack-plugin') //把公共对象复制到其中,并且可以覆盖掉公共模块中的一些配置 module.exports = merge(common, { mode: 'production', plugins: [ new CleanWebpackPlugin() new CopyWebpackPlugin(['public']) ] }) //需要合并webpack配置的需求 yarn add webpack-merge --dev
运行webpack的时候需要--config添加指定的文件名
yarn webpack --config webpack.prod.js
命令也可以定义到package.json中
//package.json "scripts": { "build": "webpack --config webpack.prod.js" }
yarn build
为代码注入全局成员
process.env.NODE_ENV //第三方模块都是针对这个成员去判断当前的运行环境,决定是否去执行打印日志等操作
//webpack.config.js const webpack = require('webpack') module.exports = { plugin: [ new webpack.DefinePlugin({ //API_BASE_URL: '"http://api.example.com"' //为代码注入一个API服务地址 API_BASE_URL: JSON.stringify('http://api.example.com') //可以先转换成表示这个值的代码片段 }) //此对象每一个键值都会注入到代码中 ] }
//main.js console.log(API_BASE_URL)//http://api.example.com
摇掉代码中未引用的部分 / 未引用代码(dead-code)
未引用代码(dead-code)
比如一些console.log语句
yarn webpack --mode production
冗余代码并没有输出
Tree-shaking不是指某个配置选项,是一组功能搭配使用后的优化效果 production模式下自动开启
Tree-shaking不是指某个配置选项,是一组功能搭配使用后的优化效果
production模式下自动开启
在其它模式开启的办法:
//webpack.config.js module.exports = { mode: 'none', ... optimization: { usedExports: true, //在导出里只导出外部使用的成员 - 负责标记”枯树叶“ minimize: true //未引用的代码都被移除掉了 - 负责摇掉 } }
作用域提升
尽可能将所有模块合并输出到一个函数中
既提升了运行效率,又减少了代码体积
Tree Shaking前提是ES Modules
由webpack打包的代码必须使用ESM
为了转换代码中的ECMAScirpt新特性,会使用babel-loader处理JS,将ESM 转换为 CommonJS
babel-loader
//webpack.config.js module: { rules: [ test: /\.js$/, use: { loader: 'babel-loader', options: { presets: [['@babel/preset-env', {modules: 'commonjs'}]] //强制配置babel } } ] }
## sideEffects - 确保你的代码没有副作用 ```json //webpack.json "sideEffects": [ './src/extend.js', '*.css' ]
分包,按需加载
按照不同的规则打包到不同的bundle,从而提高应用的响应速度
适用于多页应用程序,一个页面对应一个打包入口,公共部分单独提取
配置entry
entry
//webpack.config.js entry: { index: './src/index.js', album: './src/album.js' } //对象中,一个属性就是一个打包入口 output: { filename: '[name].bundle.js' } //动态替换成入口文件名 plugin: [ new HtmlWebpackPlugin({ ... filename: 'index.html' chunks:['index'] }) new HtmlWebpackPlugin({ ... filename: 'index.html', chunks:['album'] }) ] //为两个页面配置两个不同的chunk
我们期待一个页面使用一个对应结果,配置chunk之后会输出两个页面index.html / album.html
问题: 不同入口中肯定有公共模块被import
//webpack.config.js optimization: { splitChunks: { chunks: 'all' } }
会生成两个相同模块的公共部分album~index.html.js
album~index.html.js
按需加载,需要用到某个模块时,再加载这个模块
动态导入的模块会被自动分包
自动分包
//index.js if(hash === '#posts'){ import('./posts/posts').then(({default: posts}) => { mainElement.appendChild(posts()) //创建界面上的元素 }) }else if(hash === '#album') { import('./album/album').then(({default:album}) => { mainElement.appendChild(album()) }) }
给bundle分包进行命名
//index.js if(hash === '#posts'){ import(/* webpackChunkName:'posts' */,'./posts/posts').then(({default: posts}) => { mainElement.appendChild(posts()) //创建界面上的元素 }) }else if(hash === '#album') { import(/* webpackChunkName:'album' */,'./album/album').then(({default:album}) => { mainElement.appendChild(album()) }) }
生成的bundle就会用注释的名称起名
提取CSS到单个文件
//webpack.config.js const MiniCssExtractPlugin = require('html-webpack-plugin') module: { rules: [ test: /\.css$/, use: [ MiniCssExtractPlugin.loader, //将样式通过style标签注入 ] ] }
css超过50kb才考虑单独提取
压缩输出的CSS文件
yarn add optimize-css-assets-webpack-plugin --dev
//webpack.config.js const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin') plugin: [ new OptimizeCssAssetsWebpackPlugin() ]
官方建议压缩插件配置到minimizer中
minimizer
//webpack.config.js optimization: { minimizer: [ new OptimizeCssAssetsWebpackPlugin() ] }
此时,默认JS文件不被压缩了,要重新配置内置的terser-webpack-plugin
terser-webpack-plugin
yarn add terser-webpack-plugin --dev
//webpack.config.js const TerserWebpackPlugin = require('terser-webpack-plugin') optimization: { minimizer: [ new TerserWebpackPlugin() ] }
JS和CSS文件都被压缩了
部署文件,会启动静态资源缓存,对于用户的浏览器而言,会缓存住静态资源,后续就不用再请求服务器得到静态资源文件。
不过,开启客户端的静态资源缓存,如果在缓存策略中,缓存失效时间过短,效果不会很明显,时间过长,更新无效
建议,生成模式下,文件名使用Hash,一旦资源文件发生改变,文件名称也会发生变化
Hash
文件名称
//webpack.config.js output / MiniCssExtractPlugin : //filename: '[name]-[hash].bundle.xx' filename: '[name]-[contenthash:8].bundle.xxx' //根据输出文件内容输出哈希值,不同的文件就有不同的哈希值,最适合解决缓存问题
yarn add rollup --dev
参数指定打包入口文件 + 输出格式 + 输出文件路径
入口文件
输出格式
输出文件路径
yarn rollup ./src/index/js --format iife --file dist
//rollup.config.js //导出一个配置对象 export default { input: 'src/index.js', output: { file: 'dist/bundle.js',//输出文件名 format: 'iife',//输出格式 } }
需要指定配置文件读取:
yarn rollup --config
或者指定不同配置文件的名称
yarn rollup --config rollup.config.js
扩展,且插件时rollup唯一扩展途径:
e.g.rollup-plugin-json:加载json类型模块
rollup-plugin-json
yarn add rollup-plugin-json --dev
//rollup.config.js import json from 'rollup-plugin-json' plugin:[ json() //将调用结果放在数组中 ]
可以直接使用package.json中的参数
package.json
//index.js import {name,version} from '../package.json' log(name)
在打包结果bundle.js里就出现了name字段的参数
bundle.js
rollup-plugin-node-resolve:直接使用模块名称导入对应模块
rollup-plugin-node-resolve
yarn add rollup-plugin-node-resolve --dev
//rollup.config.js import resolve from 'rollup-plugin-node-resolve' plugin: [ resolve() ]
直接可以导入npm模块:
//index.js import _ from 'loadsh-es' log(_.camelCase('111'))
rollup-plugin-commonjs
//rollup.config.js import commonjs from 'rollup-plugin-commonjs' plugins: [ commonjs() ]
新建cjs-module.js
cjs-module.js
module.exports = { foo: 'bar' }
//index.js import cjs from './cjs-module'//默认导出 log(cjs)
打包后,bundle.js以默认对象导出了结果
Dynamie Imports:动态导入,按需加载
Dynamie Imports
Code Splitting:自动处理代码的拆分
Code Splitting
//index.js import('./logger').then({log}) => { //结构的方式提取log方法 log('111') }
不允许IIFE输出格式,要实现代码拆分必须使用amd(浏览器环境)或者commojs标准
要实现代码拆分必须使用amd(浏览器环境)或者commojs标准
yarn rollup --config --format amd //覆盖掉文件的format设置
当输出多个文件时,不允许使用file模式
//rollup.config.js export default { input: 'src/index.js', output: { dir: 'dist', format: 'amd' } }
//rollup.config.js //方式1 input: ['src/index.js','src/album.js'] //方式2 input: { foo: 'src/index.js', bar: 'src/album.js' } //内部打包不会自动提取公共模块,所以format必须是amd模式
新建dist/index.html
dist/index.html
<sript src="foo.js"></sript>
Rollup优势:
缺点:
webpack:开发应用程序
rollup:JavaScript框架、类库
yarn init
parcel
yarn add parcel-bundle --dev
src/index.html
<script src="main.js"></script>
main.js
foo.js
//foo.js export default { bar: () => { console.log('2') } }
//main.js import foo from './foo' import $ from 'jquery' import './style.css' import logo from './zce.png' $(document.body).append('<h1>hi</h1>') //打包时可以自动安装jquery模块,也可以加载其它类型模块 //也可以支持资源模块动态导入 import('jquery').then($ => { $(document.body).append(`<img src="${logo}"/>`) }) foo.bar() //使用热加载 if(module.hot){ module.hot.accept(() => { console.log('1') }) }
yarn parcel src/index.html
yarn parcel build src/index.html
软件开发需要多人协同,不同开发者具有不同的编码习惯和喜好增加项目维护成本,需要明确一个统一的标准
初始化项目
npm init --dev
安装ESLint模块为开发依赖
npm install eslint --save-dev npx eslint --version npx eslint --init
通过CLI命令验证结果
ESL检查步骤:
编写“问题代码”
使用eslint执行检测
npx eslint .\01.js --fix
完成eslint使用配置
ESLint配置文件.eslintrc参数说明 深入理解 ESlint
ESLint配置文件.eslintrc参数说明
深入理解 ESlint
eslintConfig
parserOptions:ESLint 允许你指定你想要支持的 JavaScript 语言选项。默认情况下,ESLint 支持 ECMAScript 5 语法。你可以覆盖该设置,以启用对 ECMAScript 其它版本和 JSX 的支持。
parser:ESLint 默认使用 Espree 作为其解析器,你可以在配置文件中指定一个不同的解析器,只要该解析器符合下列要求:
processor:插件可以提供处理器。处理器可以从另一种文件中提取 JavaScript 代码,然后让 ESLint 检测 JavaScript 代码。或者处理器可以在预处理中转换 JavaScript 代码。若要在配置文件中指定处理器,请使用 processor 键,并使用由插件名和处理器名组成的串接字符串加上斜杠。
env:一个环境定义了一组预定义的全局变量。
globals:当访问当前源文件内未定义的变量时,no-undef 规则将发出警告。如果你想在一个源文件里使用全局变量,推荐你在 ESLint 中定义这些全局变量,这样 ESLint 就不会发出警告了。你可以使用注释或在配置文件中定义全局变量。
plugins:ESLint 支持使用第三方插件。在使用插件之前,你必须使用 npm 安装它。
rules:ESLint 附带有大量的规则。你可以使用注释或配置文件修改你项目中要使用的规则
为了在文件注释里配置规则,使用以下格式的注释:
/* eslint eqeqeq: "off", curly: "error" */
在这个例子里,eqeqeq 规则被关闭,curly 规则被打开,定义为错误级别。你也可以使用对应的数字定义规则严重程度:
/* eslint eqeqeq: 0, curly: 2 */
这个例子和上个例子是一样的,只不过它是用的数字而不是字符串。eqeqeq 规则是关闭的,curly 规则被设置为错误级别。
如果一个规则有额外的选项,你可以使用数组字面量指定它们,比如:
/* eslint quotes: ["error", "double"], curly: 2 */
这条注释为规则 quotes 指定了 “double”选项。数组的第一项总是规则的严重程度(数字或字符串)。
settings:ESLint 支持在配置文件添加共享设置。你可以添加 settings 对象到配置文件,它将提供给每一个将被执行的规则。如果你想添加的自定义规则而且使它们可以访问到相同的信息,这将会很有用,并且很容易配置。
extends:一个配置文件可以被基础配置中的已启用的规则继承。
ESLint 附带有大量的规则,你可以在配置文件的 rules 属性中配置你想要的规则。每一条规则接受一个参数,参数的值如下:
rules
举个例子,我们先写一段使用了平等(equality)的代码,然后对 eqeqeq 规则分别进行不同的配置。
eqeqeq
// demo.js var num = 1 num == '1'
这里使用了命令行的配置方式,如果你只想对单个文件进行某个规则的校验就可以使用这种方式:
我们看下 quotes 规则,根据官网介绍,它支持字符串和对象两个配置项:
quotes
{ "rules": { // 使用数组形式,对规则进行配置 // 第一个参数为是否启用规则 // 后面的参数才是规则的配置项 "quotes": [ "error", "single", { "avoidEscape": true } ] } }
扩展就是直接使用别人已经写好的 lint 规则,方便快捷。扩展一般支持三种类型:
{ "extends": [ "eslint:recommended", "plugin:react/recommended", "eslint-config-standard", ] }
eslint:
eslint:recommended
eslint:all
plugin:
plugins
eslint-config-
eslint-config-standard
standard
如果你觉得自己的配置十分满意,也可以将自己的 lint 配置发布到 npm 包,只要将包名命名为 eslint-config-xxx 即可,同时,需要在 package.json 的 peerDependencies 字段中声明你依赖的 ESLint 的版本号。
eslint-config-xxx
//.eslintrc.js parser:'@typescript-eslint/parset' //语法解析器
在script函数,在babel之前添加插件,处理源代码:
const script = () => { return src('src/assets/script/*.js', {base: 'src'}) .pipe(plugins.eslint()) .pipe(plugins.eslint.format()) .pipe(plugins.eslint.failAfterError()) .pipe(plugins.babel({presets:[@babel/preset-env]})) ... }
完成配置文件初始化:npx eslint
npx eslint
在eslint配置文件设置:
globals: { '$': 'readonly' }
成功执行gulp任务:npx gulp script
npx gulp script
❄在babel之后、style之前配置:
//webpack.config.js rules:[ ... { test:/\.js$/, exclude:/node_modules/, enforce:'pre' } ... ]
报错,react要靠额外插件、定义规则
npm install eslint-plugin-react
//eslintrc.js rules: [ 'react/jsx-uses-react': 2 , 'react/jsx-uses-react': 2 ],//解决react定义没有使用的报错 plugins: [ 'react' ]
The text was updated successfully, but these errors were encountered:
No branches or pull requests
模块化:对代码按照功能的不同去划分不同的模块
模块化演变过程
早期:
模块化规范
构成:模块化标准 + 模块加载器
ES Modules + CommonJS
:模块化最佳实践方式ES Modules
基本特性
通过script添加
type = "module"
的属性,就可以以 ES Module 的标准执行其中的JS代码。使用:ESM自动采用严格模式,忽略
'use strict'
每个ES Module都是运行在单独的私有作用域中
ESM是通过 CORS 的方式请求外部 JS 模块的
ESM 的 script标签会延迟执行脚本
导入 / 导出
export / import
导出不是字面量
如果要导出一个字面量:
暴露出来的是一个引用关系,只读
导入一些不需要外界控制的子模块
把所有导出成员都导出来
动态导入模块机制:import...from...只能出现在最顶层
提取模块中默认成员写法
导出导入成员:如果将import改成export,那么这些成员将作为目前模块的导出成员看待;同时,目前模块也将无法访问到该导出成员
实例:当需要导入的模块过多时,可以新建一个中转文件index.js,把组件导入再导出;default参数必须要进行重命名
ESM浏览器环境Polyfill
IE不兼容ESM标签:引用browser-es-module-loader去使用ESM
引入脚本文件到ESM中:cdn服务 unpkg.com/browser-es-module-loader 拿到js文件
ESM运行原理:
ESM in node.js(过渡状态)
使用:
不支持第三方模块导出默认成员,因为第三方模块并没有向外去暴露出一个成员/第三方模块都是导出默认成员
与CommonJS交互
ESM可以导入CJS模块
CJS不能导入ESM模块
CJS始终只会导出一个默认成员
注意:import不是解构导出对象(只是一个固定的用法
与CommonJS模块的差异
新版本支持
常用的模块化打包工具 / Webpack打包
由来 / 概要
几种模块化对生产环境产生的影响:
功能:
概要
模块打包器(module bundler)
:将零散的代码打包到JS文件中模块加载器(loader)
:将兼容有问题的代码编译转换代码拆分(code splitting)
:挑选有需要的代码打包。当实际中需要到某个模块,再通过异步去加载它,实现增量加载或者渐进式加载资源模块(asset module)
:webpack支持以模块化载入任意的资源文件,例如,JavaScript中可以支持import CSS文件上手
配置文件
约定:入口文件
src/index.js
->dist/main.js
自定义:
src/main.js
在根目录下添加webpack.config.js
工作模式
也可以在配置中添加此属性
打包结果运行原理
模块私有作用域
webpack资源模块加载
安装css-loader
添加加载资源规则配置
webpack导入资源模块
打包入口 ≈ 运行入口
JavaScript驱动整个前端应用的业务
webpack建议在js引入css文件,原因:
根据代码的需要动态导入资源
需要资源的不是应用,而是代码
意义:
文件资源加载器(file-loader)
示例:
Data URLs 与 url-loader
Data URLs:以代码的形式直接表示一个文件
不需要独立的物理文件了
合适打包体积小的资源,体积过大打包文件就过大,运行时间长
使用:
安装url-loader
配置
常用加载器分类
编译转换类
:css-loder以JS形式工作的css模块文件操作类
:flie-loader导出文件访问路径代码检查类
:eslint-loader检查通过/不通过webpack编译ES6
使用babel-loader编译代码
模块加载方式
webpack兼容多种模块化标准:
遵循ES Modules标准的import声明
遵循CommonJS标准的require函数
遵循AMD标准的define函数和require函数
@import指令
和url函数
核心工作原理
依赖树
webpack递归此依赖树,找到对应节点资源文件,根据rule使用加载器加载这个模块,最后的结果放到bundle.js中
loader机制是webpack的核心
开发一个loader
示例:css-loader → style-loader
webpack插件机制 / plugin
loader专注实现资源模块加载
plugin解决其他自动化工作:清除dist目录、拷贝静态文件至输出目录、压缩输出代码
自动清除输出目录插件 / clean-webpack-plugin
每次执行前就可以清除dist目录的文件了
自动生成html插件 / html-webpack-plugin
自动生成使用bundle.js的html
最后会输出一个index.html文件到打包目录
静态文件拷贝 / copy-webpack-plugin
将public里的文件完整拷贝到输出目录
插件机制的工作原理
plugin通过
钩子机制
实现webpack要求插件必须是一个函数或者一个包含
apply方法
的对象eg:清除bundle.js中不必要的注释
借用emit()钩子
理想的开发环境
自动编译
watch
工作模式:监听文件变化,自动重新打包自动刷新浏览器
BrowserSync
自动刷新功能缺:麻烦、效率低
Webpack Dev Server
Webpack Dev Server
提供用于开发的HTTP Server:集成自动编译和自动刷新浏览器特点:为了工作效率没有将结果打包到磁盘中,减少磁盘的读写操作
静态文件:开发阶段不需要参与webpack构建,但同样需要被served
默认只会serve打包输出文件
只要是webpack输出的文件,都可以直接被访问
其它资源文件也需要serve
代理API
解决开发环境接口跨域请求问题
解决方式1:
跨域资源共享(CORS)
,使用的CORS的前提是API必须支持,并不是任何情况下API都应该支持。如果同源部署就没有必要使用CORS解决方式2:
Webpack Dev Server
支持配置代理目标:将github API代理到开发服务器
Endpoint可以理解为接口端点/入口
Source Map / 源代码地图
解决:运行代码与源代码之间完全不同,无法调试应用,错误信息无法定位。调试和报错都是基于运行代码
Source Map
是一个独立的map文件,与源码在同一个目录下运行代码通过Source Map逆向解析得到源代码
例子:新版jquery.min.js文件手动添加注释
//# sourceMappingURL = jquery-3.4.1.min.map
Source Map解决了源代码与运行代码不一致所产生的我问题
webpack配置Source Map
基本使用:
bundle.js文件最后也自动加上注释
eval模式下的Source map
设置成eval模式:
webpack devtool模式对比
eval
:只能定位哪一个文件除了错误eval-source-map
:同样使用eval模式执行代码,可以定位到行和列的信息。相比eval生成了source-mapcheap-eval-source-map
:只能定位到行信息,快一点,显示ES6转换后的代码cheap-module-eval-source-map
:也定位到行,但显示的是我们写的源代码没有经过loader加工inline-source-map
:普遍的source-map文件都是以物理文件存在,而该模式是使用dataURL的方式嵌入到代码中。代码体积大很多,不常用hidden-source-map
:构建当中生成了该文件但不展示nosources-source-map
:没有源代码但是提供了行列信息。以上两者保护生产环境代码不被暴露如何选择Source Map模式(个人)
开发模式:cheap-module-eval-source-map
生产模式:不生成source-map
调式阶段:nosources-source-map,定位到源代码位置,不至于向外暴露你源代码的内容
模块热替换 HMR
自动刷新
:不丢失当前页面状态HMR集成在
webpack-dev-server
中或者在配置文件中开启
特点
HMR APIs
webpack处理js模块热替换
eg:针对editor模块处理热替换
更加通用的热替换:图片模块热替换
HMR注意事项
处理HMR的代码报错会导致自动刷新:使用
hotOnly
没启用HMR的情况下,HMR API报错
因为没有
module.hot
这个对象代码中多了一些与业务无关的代码,但不会影响生产环境
生产环境优化
生产环境注重运行效率——
模式(mode)
为不同的工作环境创建不同的配置:
配置文件根据环境不同导出不同配置
webpack支持导出一个函数,这个函数返回一个配置对象
不同环境对应不同配置文件
公共配置:
生产环境配置:
运行webpack的时候需要--config添加指定的文件名
命令也可以定义到package.json中
Webpack DefinePlugin
为代码注入全局成员
Tree-shaking
摇掉代码中未引用的部分 /
未引用代码(dead-code)
比如一些console.log语句
冗余代码并没有输出
在其它模式开启的办法:
webpack合并模块 / Scope Hoisting
作用域提升
尽可能将所有模块合并输出到一个函数中
既提升了运行效率,又减少了代码体积
Tree Shaking与Babel
Tree Shaking前提是ES Modules
由webpack打包的代码必须使用ESM
为了转换代码中的ECMAScirpt新特性,会使用
babel-loader
处理JS,将ESM 转换为 CommonJScode-splitting / 代码分包-代码分割
分包,按需加载
多入口打包
适用于多页应用程序,一个页面对应一个打包入口,公共部分单独提取
配置
entry
我们期待一个页面使用一个对应结果,配置chunk之后会输出两个页面index.html / album.html
提取公共模块
会生成两个相同模块的公共部分
album~index.html.js
动态导入
按需加载,需要用到某个模块时,再加载这个模块
动态导入的模块会被
自动分包
魔法注释
给bundle分包进行命名
生成的bundle就会用注释的名称起名
MiniCssExtractPlugin
提取CSS到单个文件
css超过50kb才考虑单独提取
OptimizeCssAssetsWebpackPlugin
压缩输出的CSS文件
官方建议压缩插件配置到
minimizer
中此时,默认JS文件不被压缩了,要重新配置内置的
terser-webpack-plugin
JS和CSS文件都被压缩了
输出文件名Hash
部署文件,会启动静态资源缓存,对于用户的浏览器而言,会缓存住静态资源,后续就不用再请求服务器得到静态资源文件。
不过,开启客户端的静态资源缓存,如果在缓存策略中,缓存失效时间过短,效果不会很明显,时间过长,更新无效
建议,生成模式下,文件名使用
Hash
,一旦资源文件发生改变,文件名称
也会发生变化Rollup
介绍
上手
参数指定打包
入口文件
+输出格式
+输出文件路径
配置文件
需要指定配置文件读取:
或者指定不同配置文件的名称
使用插件
扩展,且插件时rollup唯一扩展途径:
e.g.
rollup-plugin-json
:加载json类型模块可以直接使用
package.json
中的参数在打包结果
bundle.js
里就出现了name字段的参数加载NPM模块
rollup-plugin-node-resolve
:直接使用模块名称导入对应模块直接可以导入npm模块:
加载commonJS
rollup-plugin-commonjs
新建
cjs-module.js
打包后,
bundle.js
以默认对象导出了结果代码拆分
Dynamie Imports
:动态导入,按需加载Code Splitting
:自动处理代码的拆分不允许IIFE输出格式,
要实现代码拆分必须使用amd(浏览器环境)或者commojs标准
当输出多个文件时,不允许使用file模式
多入口打包
新建
dist/index.html
Rollup / Webpack 选用规则
Rollup优势:
缺点:
webpack:开发应用程序
rollup:JavaScript框架、类库
Parcel
package.json
parcel
src/index.html
编写开发阶段的源代码,也是打包的入口文件main.js
和foo.js
规范化标准
ESLint结合webpack
ESLint工具使用
初始化项目
安装ESLint模块为开发依赖
通过CLI命令验证结果
ESL检查步骤:
编写“问题代码”
使用eslint执行检测
完成eslint使用配置
ESLint配置文件解析
eslintConfig
属性配置parserOptions:ESLint 允许你指定你想要支持的 JavaScript 语言选项。默认情况下,ESLint 支持 ECMAScript 5 语法。你可以覆盖该设置,以启用对 ECMAScript 其它版本和 JSX 的支持。
parser:ESLint 默认使用 Espree 作为其解析器,你可以在配置文件中指定一个不同的解析器,只要该解析器符合下列要求:
processor:插件可以提供处理器。处理器可以从另一种文件中提取 JavaScript 代码,然后让 ESLint 检测 JavaScript 代码。或者处理器可以在预处理中转换 JavaScript 代码。若要在配置文件中指定处理器,请使用 processor 键,并使用由插件名和处理器名组成的串接字符串加上斜杠。
env:一个环境定义了一组预定义的全局变量。
globals:当访问当前源文件内未定义的变量时,no-undef 规则将发出警告。如果你想在一个源文件里使用全局变量,推荐你在 ESLint 中定义这些全局变量,这样 ESLint 就不会发出警告了。你可以使用注释或在配置文件中定义全局变量。
plugins:ESLint 支持使用第三方插件。在使用插件之前,你必须使用 npm 安装它。
rules:ESLint 附带有大量的规则。你可以使用注释或配置文件修改你项目中要使用的规则
为了在文件注释里配置规则,使用以下格式的注释:
/* eslint eqeqeq: "off", curly: "error" */
在这个例子里,eqeqeq 规则被关闭,curly 规则被打开,定义为错误级别。你也可以使用对应的数字定义规则严重程度:
/* eslint eqeqeq: 0, curly: 2 */
这个例子和上个例子是一样的,只不过它是用的数字而不是字符串。eqeqeq 规则是关闭的,curly 规则被设置为错误级别。
如果一个规则有额外的选项,你可以使用数组字面量指定它们,比如:
/* eslint quotes: ["error", "double"], curly: 2 */
这条注释为规则 quotes 指定了 “double”选项。数组的第一项总是规则的严重程度(数字或字符串)。
settings:ESLint 支持在配置文件添加共享设置。你可以添加 settings 对象到配置文件,它将提供给每一个将被执行的规则。如果你想添加的自定义规则而且使它们可以访问到相同的信息,这将会很有用,并且很容易配置。
extends:一个配置文件可以被基础配置中的已启用的规则继承。
定制ESLint校验规则
ESLint 附带有大量的规则,你可以在配置文件的
rules
属性中配置你想要的规则。每一条规则接受一个参数,参数的值如下:举个例子,我们先写一段使用了平等(equality)的代码,然后对
eqeqeq
规则分别进行不同的配置。这里使用了命令行的配置方式,如果你只想对单个文件进行某个规则的校验就可以使用这种方式:
我们看下
quotes
规则,根据官网介绍,它支持字符串和对象两个配置项:扩展
扩展就是直接使用别人已经写好的 lint 规则,方便快捷。扩展一般支持三种类型:
eslint:
开头的是 ESLint 官方的扩展,一共有两个:eslint:recommended
、eslint:all
。plugin:
开头的是扩展是插件类型,也可以直接在plugins
属性中进行设置,后面一节会详细讲到。eslint-config-
开头,使用时可以省略这个头,上面案例中eslint-config-standard
可以直接简写成standard
。如果你觉得自己的配置十分满意,也可以将自己的 lint 配置发布到 npm 包,只要将包名命名为
eslint-config-xxx
即可,同时,需要在 package.json 的 peerDependencies 字段中声明你依赖的 ESLint 的版本号。ESLint对TypeScript支持
ESLint结合自动化工具或者Webpack
结合自动化工具gulp
在script函数,在babel之前添加插件,处理源代码:
完成配置文件初始化:
npx eslint
在eslint配置文件设置:
成功执行gulp任务:
npx gulp script
结合webpack,通过loader引入
❄在babel之后、style之前配置:
报错,react要靠额外插件、定义规则
基于ESLint的衍生工具
Stylelint工具的使用
The text was updated successfully, but these errors were encountered: