浏览器模块化的问题:
- 效率问题:
- 精细的模块划分带来了更多的JS文件,更多的js文件带来了更多的请求,降低了页面访问效率
- 兼容性问题:
- 浏览器目前仅支持ES6的模块化标准,并且还存在兼容性问题
- 工具问题:
- 浏览器不支持npm下载的第三方包
浏览器模块化的这些问题在node端没有那么明显,因为在node端,运行的JS文件在本地,因此可以本地
读取文件,它的效率比浏览器远程传输文件高得多
根本原因
在浏览器端,开发时态(devtime)和运行时态(runtime)的侧重点不同
- 开发时态devtime
- 模块划分越细越好
- 支持多种模块化标准
- 支持npm或其他包管理器下载的模块
- 能够解决其他工程化的问题
- 运行时态runtime
- 文件越少越好
- 文件体积越小越好
- 代码内容越乱越好(防止他人用)
- 所有浏览器都要兼容
解决方法
构建工具:将开发时态编写的代码转换为运行时态需要的东西
webpack安装和使用
简介
webpack是基于模块化的打包工具,它把一切视为模块,它通过一个开发时态的入口模块为起点,分析出所有的依赖关系,经过一系列的压缩、合并,最终生成运行时态的文件
特点
- 为前端工程化而生
- 简单易用
- 强大的生态:提供了可以扩展其功能的机制,使很多第三方库可以融入webpack中
- 基于node.js:在构建过程中需要读取文件
- 基于模块化:在构建过程中要分析依赖关系,方式是通过模块化导入语句进行分析的,支持各种模块化标准,包括但不局限于CommonJS,ES6 Module(模块化)
安装
通过npm安装,提供了两个包
- webpack:核心包,包含了webpack构建过程中要用到的所有api
- webpack-cli:提供一个简单的cli命令,它调用了webpack核心包的api,来完成构建过程,推荐本地安装
使用
- 直接使用webpack命令,默认情况下,webpack会以./src/index.js作为入口文件分析依赖环境,打包到
./dist/main.js文件中 - 通过—mode选项可以控制webpack的打包结果的运行环境
- npx webpack —mode=development —watch 开发环境的打包(方便调试)
- npx webpack —mode=production 生产环境的打包(压缩)
编译结果分析
(function (modules) {var moduleExports = {}; //用于缓存模块的导出结果//require函数相当于是运行一个模块,得到模块导出结果function __webpack_require(moduleId) { //moduleId就是模块的路径if (moduleExports[moduleId]) {//检查是否有缓存return moduleExports[moduleId];}var func = modules[moduleId]; //得到该模块对应的函数var module = {exports: {}}func(module, module.exports, __webpack_require); //运行模块var result = module.exports; //得到模块导出的结果moduleExports[moduleId] = result; //缓存起来return result;}//执行入口模块return __webpack_require("./src/index.js"); //require函数相当于是运行一个模块,得到模块导出结果})({ //该对象保存了所有的模块,以及模块对应的代码"./src/a.js": function (module, exports) {eval("console.log(\"module a\")\nmodule.exports = \"a\";\n //# sourceURL=webpack:///./src/a.js")},"./src/index.js": function (module, exports, __webpack_require) {eval("console.log(\"index module\")\nvar a = __webpack_require(\"./src/a.js\")\na.abc();\nconsole.log(a)\n //# sourceURL=webpack:///./src/index.js")}});
配置文件
- 配置文件是要参与运行的,只能使用CommonJS模块化
- 打包的文件只是分析依赖关系,不参与运行
- 默认情况下,webpack会读取webpack.config.js文件作为配置文件,也可以通过CLI参数—config来指定某个配置文件,当命令行参数与配置文件中的配置出现冲突时,以命令行参数为准。
基本配置
- mode:编译模式,字符串,取值为development或production,指定编译结果代码运行的环境,会影响webpack对编译结果代码格式的处理
- entry:入口,字符串,指定入口文件
- output:出口,对象,指定编译结果文件
devtool配置
source map实际上是一个配置,配置中不仅记录了所有源码内容,还记录了和转换后的代码的对应关系
浏览器处理source map原理
- 浏览器请求bundle.js文件
- 浏览器发现有source map
- 浏览器再次请求bundle.map文件
- bundle.map文件记录了原始代码,还记录了转换后的代码和原始代码的对应关系
编译过程
- 找到入口文件
- 加载模块文件
- 检查记录(缓存,已记录则结束,未记录则继续)
- 读取文件内容
- 语法分析(loader转换代码发生在这里)
- AST抽象语法树
- 记录依赖(保存到dependencies中)
- 替换依赖函数
- 保存转换后的模块代码
入口和出口的最佳实践
loader
loader的功能定位是代码转换
module.exports = {mode: "development",module: {// 从下往上从右往左匹配rules: [{test: /index\.js$/, // 正则表达式,匹配模块的路径use: [{loader: "loader路径",options: {// 在loaders代码中获取options// loader-utils第三方库// require("loader-utils")// loaderUtils.getOptions(this);changeVar: '参数'}}]}]}}
plugin
当需要把功能嵌入到webpack的编译流程中,需要依托plugin,本质是一个带有apply方法的对象,习惯将该对象写成构造函数的模式
module.exports = class MyPlugin{apply(compiler){}}
要将plugin应用到webpack,需要把插件对象配置到webpack的plugins数组中
var MyPlugin = require("./MyPlugin")module.exports = {plugins:[new MyPlugin()]}
- apply函数会在初始化阶段,创建好Compiler对象后运行。
- compiler对象是在初始化阶段构建的,整个webpack打包期间只有一个compiler对象,后续完成打包工作的是compiler对象内部创建的compilation
- apply方法会在创建好compiler对象后调用,并向方法传入一个compiler对象
- compiler对象提供了大量的钩子函数,可以再apply方法中使用下面的代码注册钩子函数
class MyPlugin{apply(compiler){compiler.hooks.事件名称.事件类型(name, function(compilation){//事件处理函数})}}
事件名称
即要监听的事件名,即钩子名,所有的钩子:https://www.webpackjs.com/api/compiler-hooks
事件类型
- 这一部分使用的是 Tapable API,这个小型的库是一个专门用于钩子函数监听的库。
- 它提供了一些事件类型:
- tap:注册一个同步的钩子函数,函数运行完毕则表示事件处理结束
- tapAsync:注册一个基于回调的异步的钩子函数,函数通过调用一个回调表示事件处理结束
- tapPromise:注册一个基于Promise的异步的钩子函数,函数通过返回的Promise进入已决状态表示事件处理结束
处理函数
- 处理函数有一个事件参数compilation
区分环境
开发中需要针对生产环境和开发环境分别书写webpack配置,为了更好的适应这种要求,webpack允许配置不仅可以是一个对象,还可以是一个函数
module.exports = env => {return {//配置内容}}
在开始构建时,webpack如果发现配置是一个函数,会调用该函数,将函数返回的对象作为配置内容,因此,开发者可以根据不同的环境返回不同的对象,在调用webpack函数时,webpack会向函数传入一个参数env,该参数的值来自于webpack命令中给env指定的值,例如
npx webpack --env abc # env: "abc"npx webpack --env.abc # env: {abc:true}npx webpack --env.abc=1 # env: {abc:1}npx webpack --env.abc=1 --env.bcd=2 # env: {abc:1, bcd:2}
常用扩展
- 清除输出目录
- 配置文件output: {filename: “[name].[hash:5].js”},每次运行都会生成不同的打包文件,此插件会自动清除旧的打包文件
- npm i -D clean-webpack-plugin
- 自动生成页面html-webpack-plugin
new HtmlWebpackPlugin({template: "./public/index.html",})
- 复制静态资源copy-webpack-plugin
new CopyWebpackPlugin([{from: '路径', to: '路径'}])
- 开发服务器webpack-dev-server
- 常用配置:
- port:配置监听端口
- proxy:配置代理,常用于跨域访问
- stats:配置控制台输出内容
module.exports = {stats: {modules: false,colors: true}}
css工程化
利用webpack拆分css
- css-loader, style-loader
BEM
- BEM是一套针对css类样式的命名方法
css-in-js
- css in js的核心思想是:用一个js对象来描述样式,而不是css样式表
css module
- 要启用css module,需要将css-loader的配置modules设置为true,开启了css module后,css-loader会将样式中的类名进行转换,转换为一个唯一的hash值。由于hash值是根据模块路径和类名生成的,因此,不同的css模块,哪怕具有相同的类名,转换后的hash值也不一样
预编译器less
js兼容性
babel编译器
出现原因
- 不同版本的浏览器能识别的ES标准并不相同,就导致了开发者面对不同版本的浏览器要使用不同的语言
- bable的出现,就是解决这样的问题,它是一个编译器,可以把不同标准书写的语言,编译为统一的,能被各种浏览器识别的语言
安装
babel仅提供一些分析功能,真正的转换依托于插件完成,babel可以和构建工具联合使用,也可以独立使用
如果要独立使用babel,需要安装下面两个库:
npm i -D @babel/core @babel/cli@babel/core: babel核心库,提供了编译所需的所有api@babel/cli: 提供了一个命令行工具,调用核心库的api完成编译
babel的使用
按文件编译babel 要编译的文件 -o 编辑结果文件按目录编译babel 要编译的整个目录 -d 编译结果放置的目录
babel本身没有做任何事情,真正的编译要依托于babel插件和babel预设来完成的,babel预设和postcss预设含义一样,是多个插件的集合体,用于解决一些列常见的兼容问题
