模块化开发
模块化演变过程
- 最早基于文件划分
- 每个文件就是一个模块:所有模块都在全局工作,所有的模块成员都可以在外部被访问,命名可能冲突,
- 第二阶段:每个模块只暴露一个全局对象
- 在第一阶段基础上用一个对象包裹所有模块成员
- 第三阶段:使用立即执行函数为模块提供私有空间
- 模块私有成员只能在模块内部通过闭包的方式去访问
- 第四阶段:
- CommonJS规范
- 约定每个文件就是一个模块
- 每个模块都有单独的作用域
- 通过module.exports导出成员
- 通过 require 函数载入模块
- 以同步的方式加载模块:导致在浏览器端效率低下
- AMD (Asynchronous Module Definition) 异步模块定义规范
- 通过Require.js加载模块
- 使用起来比较复杂
- 前端模块化演变的一步,一种妥协的模块化规范
- Sea.js + CMD
模块化标准或规范
目前统一成ES Modules 和 CommonJs,ES Modules是2014年(随着ES2015一起)才出现的标准
- ES Modules
- 基本特性:1、通过给script标签添加type=module的属性就可以以ES Modules 的标准执行 JS 代码
2、ESM自动采用严格模式,忽略 ‘use strict’
3、每个 ESM 都是运行在单独的作用域中
4、ESM 中外部 JS 文件是通过 CORS 的方式请求实现的
5、ESM 中的 script 标签会延迟执行脚本 - ES Modules 导入和导出
export:对外暴露接口
import:导入外部接口
//---------- //方式一 export var name = 'foo modules' //------- //方式二 var name = 'foo modules' var age = 25 export { name, age } export { name as fooname, age} //将name重命名为fooname export { name as defaule, age} // 将name定义为默认成员 //---------
- 导入导出注意事项
1、使用export导出的并不是对象字面量,import导入的也不是对对象的解构
2、导入成员是只读成员,不能修改
3、导出并不是导出值,而是导出地址,如果模块内部操作对象,外部被导出的对象也会相应改变 - import注意事项
1、import在带入对象时路径必须是完整的文件名,不能省略后缀
2、相对路径中./不能省略
3、可以使用/ 开头的绝对路径
4、可以使用完整的url来引用文件
5、 只加载和执行模块,并不提取模块
6、 导出对象中的所有成员import './modules.js' //只加载和执行模块,并不提取模块
7、import只能出现在最外层作用域,不能嵌套,可以使用import()函数import * as mod from './modules.js' //导出模块中的所有成员并作为mod对象的属性
8、同时导入默认成员和命名成员//方式1 import { name, age, defaule as title } from './modules.js' //方式2 import title, { name, age } from './modules.js'
- 直接导入导出成员
import { name, age, defaule as title } from './modules.js' //替换为 export { name, age, defaule as title } from './modules.js' // 直接导出这个文件中导入的成员,在这个文件中就不能使用这个导入的成员了,可以用来组合一些组件
- 基本特性:1、通过给script标签添加type=module的属性就可以以ES Modules 的标准执行 JS 代码
- ES Modules in Brower 兼容性问题
- nomodule:此条js文件在不支持module的浏览器中执行
- ES Modules in Node 的支持情况
- 当前node已经可以支持node,从node 8.5 之后的版本都支持
模块化打包工具
- 由来(需要前端模块化的原因)
- ES Modules 存在环境兼容情况
- 模块文件过多,网络请求频繁
- 所有的前端资源都需要模块化
- 需求
- 需要将包含新特性的代码编译为大多数浏览器支持的代码
- 将散落的模块文件打包到一起
- 需要支持不同种类的前端文件资源模块
Webpack模块化打包工具
-
Webpack配置文件
- 项目根目录下添加 webpack.config.js
const path = require('path') module.export = { mode: 'development', //webpack的工作模式 entry: './src/main.js', output: { filename: 'bundle.js', path: path.join(__dirname,'output'), pubilcPath: 'dist/' } module: { rules: [ { test: /.js$/, use: { loader: 'babel-loader', options :{ presets: ['@babel/preset-env'] } } } { test: /.css$/, use: [ 'style-loader', 'css-loader' ] }, { test: /.png$/, use: { loader: 'url-loader', options: { limit: 10 * 1024 //10kb } } }, { test: /.html$/, use: { loader: 'html-loader', options: { attrs: ['img:src','a:href'] } } } ] } }
-
打包结果的运行原理
+ -
webpack 资源模块加载
- 默认loader只能处理js文件处理其他文件需要添加相应的loader
- loader是webpack实现前端模块化的核心
-
Webpack导入资源模块
- import 需要导入的文件建立js和资源文件间的依赖关系
-
Webpack 文件资源加载器
- file-loader ,文件资源加载器
-
Webpack url加载器
- Data URLs是一种特殊的url协议,可以用来直接表示文件内容,url中的文本就是文件内容;会对图片或字体等无法直接通过文本表示的二进制文件进行base64编码,以编码过后的结果表示文件内容。
- url-loader ,适合体积小的文件。
-
Webpack 常用加载器分类
- 编译转换类加载器:将加载到的资源模块转换为js代码
- 文件操作类加载器:把加载到的资源模块拷贝到输出目录并将文件访问路径导出
- 代码质量检查类:统一代码风格、提高代码质量
-
Webpack 与 ES 2015
- 需要babel-loader 和 @babel/core @babel/preset-env 来转换ES6新特性
- webpack 默认只是打包工具,可以通过加载器来实现
-
webpack 加载资源的方式
- import: 遵循ES Modules标准
- require: 遵循CommonJS标准
- define函数和require函数: 遵循AMD标准
- 不要混合使用这些标准
- loader处理非js文件也会触发资源加载,例如:css文件中的@import和url,html中的src
-
Webpack 核心工作原理
- 根据配置找到打包入口文件
- 根据入口文件中出现的加载资源的语句,找到所有模块或依赖,形成依赖树
- 递归依赖树,找到每个节点的资源文件,根据rules属性找到loader,并加载文件。
-
开发一个loader
-
Webpack插件机制
增强webpack在项目自动化方面的能力,解决除了资源加载外的自动化工作,如清除dist目录、拷贝不需要参与打包的资源目录、压缩打包结果的输出代码。- 自动清除目录插件 clean-webpack-plugin
const {CleanWebpackPlugin} = require('clean-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') const CopyWebpackPlugin = require('copy-webpack-plugin') module.exports = { plugins: { new CleanWebpackPlugin(), //用于生成index.html new HtmlWebpackPlugin({ title: 'Webpack Plugin Sample', meta: { viewport: 'width=device-width' }, template: './src/index.html' }), //生成about.html new HtmlWebpackPlugin({ filename: 'about.html' }), new CopyWebpackPlugin([ 'public' ]) //将public目录中的所有文件拷贝到输出目录,上线前再使用 } }
- 自动生成html代码插件 html-webpack-plugin:默认自动生成index.html。
- 拷贝文件插件copy-webpack-plugin:复制文件到相应目录
-
开发一个插件
webpack采用钩子机制,在webpack工作的环节中都埋下了钩子,通过往不同环节中挂载任务就可以扩展webpack 功能。//清除webpack打包生成文件中没有用的注释 class MyPlugin{ apply(compiler) { compiler.hooks.emit.tap('MyPlugin', compilation => { //compilation打包的上下文 for (const name in compilation.assets){ if(name.endWith('.js')){ const contents = compilation.assets[name].source() const withoutComments = contents.replace(/\/\*\*+\*\//g, '') compilation.assets[name] = { source: () => withoutComments, size: () => withoutComments.length } } } }) } }
-
Webpack 开发体验问题
- 理想的开发环境:能够使用http服务运行;修改源代码后webpack自动构建且浏览器自动刷新;能够提供sourcemap支持,快速定位源代码。
- Webpack 实现自动编译
watch 工作模式:监听文件变化,自动重新打包
yarn webpack --watch
- Webpack 自动刷新浏览器
Broswersync - Webpack Dev Server
提供了开发服务器且将自动编译、自动刷新浏览器等功能都集成在工具内
yarn add webpack-dev-server --dev yarn webpack-dev-server --open
- Webpack Dev Server代理API
//webpack配置文件中配置如下,可以访问public中的静态文件 devServer: { contentBase:['public'], proxy:{ '/api': { targer: 'https://api.github.com', pathRewrite: { '^/api':'' }, // 不能使用localhost:8080作为请求GitHub的主机名 changeOrigin: true } } }
- Source Map 转换前代码和转换后代码的映射,解决前端引入构建编译之后前端编写的源代码和运行的代码不一样产生的调试的问题。在.map文件最后加上注释//# sourceMappingURL = map文件名,就可在浏览器开发人员工具中解析出源代码便于调试。
- Webpack 配置Source Map
// webpack配置文件中设置如下 devtool: 'source-map'
- Webpack devtool 中的不同模式
eval模式:构建速度最快,但是效果最差,只能定位源代码文件的名称,但是不能定位行列。
eval-source-map:定位错误出现的文件并定位行列。
cheap-eval-source-map:阉割版source-map,只能定位行,不能定位列
cheap-module-eval-source-map:输出为源代码的模式
inline-source-map:将source-map以dataURL嵌入代码中
hidden-source-map:在开发工具中看不到source文件
nosources-source-map:能看到错误但是看不到源代码,只是提供行列信息,为了在生产环境中源代码不会被暴露 - 选择合适的source map
开发阶段:cheap-module-eval-source-map
生产环境:none或nosources-source-map
-
Webpack 自动刷新问题
- Webpack HMR模式:集成在webpack-dev-server
const webpack = require('webpack') //webpack配置文件中 devServer: { hotOnly: true } plugins: [ new webpack.HotModuleReplacementPlugin() ]
- HMR API
在入口文件中处理模块文件热更新后的操作
// 入口文件中 module.hot.accept('js文件路径', () => { //当js文件更新后在这里手动处理热替换逻辑 })
- HMR 注意事项
- 处理HMR的代码报错会导致自动刷新后看不到错误信息,将hot改为hotOnly
- 没启用HMR的情况下HMR API报错,使用if语句判断是否开启
- 代码中多了与业务无关的代码,在打包后都会自动删除
-
webpack 生产环境优化
- 用代码判断不同环境并为对应环境导出不同配置
module.exports = (env, argv) => { const config = {...} if (env === 'production') { config.mode = 'production' config.devtool = false config.plugins = [ ...config.plugins, new CleanWebpackPlugin(), new CopyWebpackPlugin(['plubic']) ] } return config }
- 不同环境对应不同配置文件,webpack.common.js,webpack.dev.js,webpack.prod.js,三个文件分别对应公共配置文件,开发环境配置文件和生产环境配置文件
//使用webpack-merge合并配置文件,使用yarn add webpack-merge --dev 安装 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 DefinePlugin:可以手动写入打包后代码中的部分值
const webpack = require('webpack') module.exports = { mode: 'none', entry: './src/main.js', output: { filename: 'bundle.js' }, plugins: [ new webpack.DefinePlugin({ // API_BASE_URL: '"https://api.example.com"' API_BASE_URL:JSON.stringify('https://api.example.com') }) ] }
-
Webpack Tree-shaking:检测代码中未引用的代码并去除它们,在生产模式线下自动开启。是webpack一组功能搭配使用的效果。
- 开启方法:在webpack配置文件中如下
//集中配置webpack内部优化功能 optimization:{ sideEffects: true // usedExports: true //在输出结果中只导出外部使用过的成员 concatenateModules: true // 尽可能将所有模块合并到一起并输出到一个函数中,提升效率减小体积,webpack3中的特性 minimize: true // 压缩输出后的代码 }
- Tree-shaking与babel-loader的关系:最新版本的babel-loader并不会导致tree-shaking失效,如果不确定,可以关闭presetenv中的module
-
sideEffects
- 允许通过配置的方式标识代码是否有副作用,副作用只除了导出成员是否还有其他作用。
- 默认在prod模式下是开启的。
-
Webpack 代码分割/分包
- 多入口打包:适用于多页应用程序,一般一个页面一个入口,公共的部分提取到公共部分,具体方法为将配置文件中的entry属性配置为对象,每个对象就是一个入口文件,output中的输出文件名可以使用[name]的占位符方式,plugins中配置chunks属性使生成的html页面中只引用对应的的js文件;。
const path = require('path') module.export = { mode: 'development', //webpack的工作模式 entry: { index:'./src/index.js', album:'./src/about.js' //配置多个打包入口 }, output: { filename: '[name].bundle.js', path: path.join(__dirname,'output'), publicPath: 'dist/' }, optimization: { splitChunks: { chunks: 'all' //将所有的公共模块都提取到单独的bundle中 } } module: { rules: [ { test: /\.js$/, use: { loader: 'babel-loader', options :{ presets: ['@babel/preset-env'] } } } { test: /\.css$/, use: [ 'style-loader', 'css-loader' ] }, { test: /\.png$/, use: { loader: 'url-loader', options: { limit: 10 * 1024 //10kb } } }, { test: /\.html$/, use: { loader: 'html-loader', options: { attrs: ['img:src','a:href'] } } } ] } plugins: { new CleanWebpackPlugin(), //用于生成index.html new HtmlWebpackPlugin({ title: 'Webpack Plugin Sample', meta: { viewport: 'width=device-width' }, template: './src/index.html', chunks: ['index'] }), //生成about.html new HtmlWebpackPlugin({ filename: 'about.html', chunks: ['about'] }), new CopyWebpackPlugin([ 'public' ]) //将public目录中的所有文件拷贝到输出目录,上线前再使用 } }
- 动态导入:实现模块的按需加载,动态导入的模块会被自动分包,使用方法是在文件内部使用import关键字导入组件,使用/* webpackChunkName: ‘name’*/为分包的文件命名,如果不同分包文件命名相同,就会将这些分包打包到一个文件中。
-
MiniCssExtractPlugin:实现css的按需加载
- 将样式单独存放在文件中,不像style-loader那样放在html中(css文件超过150kb再提取到单独文件)
-
OptimizeCssAssetsWebpackPlugin:压缩样式文件
-
输出文件名Hash
- 通过占位符为filename设置hash
filename: '[name]-[hash].bundle.css' //每次打包所有文件名都会改变 filename: '[name]-[chunkhash].bundle.css' //修改的文件的同一路chunk文件名都会改变 filename: '[name]-[contenthash:8].bundle.css' // 只更新变化的文件的hash名,通过冒号加数字指定hash的长度
-
Rollup 打包器 和Parcel打包器也可了解
规范化标准
- 为什么会有规范化:软件开发需要多人协同,需要统一标准
- 开发过程中人为编写的成果物都需要规范化,其中代码规范化最为重要
- 实施规范化的方法
- 人为约束
- 通过工具实现Lint
ESLint
- 监视JS代码质量、统一开发者的风格、提升编码能力
- ESLint安装
yarn add eslint --dev