浏览器模块化的问题:

  1. 效率问题:
  • 精细的模块划分带来了更多的JS文件,更多的js文件带来了更多的请求,降低了页面访问效率
  1. 兼容性问题:
  • 浏览器目前仅支持ES6的模块化标准,并且还存在兼容性问题
  1. 工具问题:
  • 浏览器不支持npm下载的第三方包

浏览器模块化的这些问题在node端没有那么明显,因为在node端,运行的JS文件在本地,因此可以本地
读取文件,它的效率比浏览器远程传输文件高得多

根本原因

在浏览器端,开发时态(devtime)和运行时态(runtime)的侧重点不同

  • 开发时态devtime
    1. 模块划分越细越好
    2. 支持多种模块化标准
    3. 支持npm或其他包管理器下载的模块
    4. 能够解决其他工程化的问题
  • 运行时态runtime
    1. 文件越少越好
    2. 文件体积越小越好
    3. 代码内容越乱越好(防止他人用)
    4. 所有浏览器都要兼容

解决方法

构建工具:将开发时态编写的代码转换为运行时态需要的东西

webpack安装和使用

简介

webpack是基于模块化的打包工具,它把一切视为模块,它通过一个开发时态的入口模块为起点,分析出所有的依赖关系,经过一系列的压缩、合并,最终生成运行时态的文件

特点

  1. 为前端工程化而生
  2. 简单易用
  3. 强大的生态:提供了可以扩展其功能的机制,使很多第三方库可以融入webpack中
  4. 基于node.js:在构建过程中需要读取文件
  5. 基于模块化:在构建过程中要分析依赖关系,方式是通过模块化导入语句进行分析的,支持各种模块化标准,包括但不局限于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 生产环境的打包(压缩)

编译结果分析

  1. (function (modules) {
  2. var moduleExports = {}; //用于缓存模块的导出结果
  3. //require函数相当于是运行一个模块,得到模块导出结果
  4. function __webpack_require(moduleId) { //moduleId就是模块的路径
  5. if (moduleExports[moduleId]) {
  6. //检查是否有缓存
  7. return moduleExports[moduleId];
  8. }
  9. var func = modules[moduleId]; //得到该模块对应的函数
  10. var module = {
  11. exports: {}
  12. }
  13. func(module, module.exports, __webpack_require); //运行模块
  14. var result = module.exports; //得到模块导出的结果
  15. moduleExports[moduleId] = result; //缓存起来
  16. return result;
  17. }
  18. //执行入口模块
  19. return __webpack_require("./src/index.js"); //require函数相当于是运行一个模块,得到模块导出结果
  20. })({ //该对象保存了所有的模块,以及模块对应的代码
  21. "./src/a.js": function (module, exports) {
  22. eval("console.log(\"module a\")\nmodule.exports = \"a\";\n //# sourceURL=webpack:///./src/a.js")
  23. },
  24. "./src/index.js": function (module, exports, __webpack_require) {
  25. eval("console.log(\"index module\")\nvar a = __webpack_require(\"./src/a.js\")\na.abc();\nconsole.log(a)\n //# sourceURL=webpack:///./src/index.js")
  26. }
  27. });

配置文件

  • 配置文件是要参与运行的,只能使用CommonJS模块化
  • 打包的文件只是分析依赖关系,不参与运行
  • 默认情况下,webpack会读取webpack.config.js文件作为配置文件,也可以通过CLI参数—config来指定某个配置文件,当命令行参数与配置文件中的配置出现冲突时,以命令行参数为准。

基本配置

  1. mode:编译模式,字符串,取值为development或production,指定编译结果代码运行的环境,会影响webpack对编译结果代码格式的处理
  2. entry:入口,字符串,指定入口文件
  3. output:出口,对象,指定编译结果文件

devtool配置

source map实际上是一个配置,配置中不仅记录了所有源码内容,还记录了和转换后的代码的对应关系

浏览器处理source map原理

  1. 浏览器请求bundle.js文件
  2. 浏览器发现有source map
  3. 浏览器再次请求bundle.map文件
  • bundle.map文件记录了原始代码,还记录了转换后的代码和原始代码的对应关系

编译过程

  • 找到入口文件
  • 加载模块文件
  • 检查记录(缓存,已记录则结束,未记录则继续)
  • 读取文件内容
  • 语法分析(loader转换代码发生在这里)
  • AST抽象语法树
  • 记录依赖(保存到dependencies中)
  • 替换依赖函数
  • 保存转换后的模块代码

入口和出口的最佳实践

loader

loader的功能定位是代码转换

  1. module.exports = {
  2. mode: "development",
  3. module: {
  4. // 从下往上从右往左匹配
  5. rules: [
  6. {
  7. test: /index\.js$/, // 正则表达式,匹配模块的路径
  8. use: [
  9. {
  10. loader: "loader路径"
  11. options: {
  12. // 在loaders代码中获取options
  13. // loader-utils第三方库
  14. // require("loader-utils")
  15. // loaderUtils.getOptions(this);
  16. changeVar: '参数'
  17. }
  18. }
  19. ]
  20. }
  21. ]
  22. }
  23. }

plugin

当需要把功能嵌入到webpack的编译流程中,需要依托plugin,本质是一个带有apply方法的对象,习惯将该对象写成构造函数的模式

  1. module.exports = class MyPlugin{
  2. apply(compiler){
  3. }
  4. }

要将plugin应用到webpack,需要把插件对象配置到webpack的plugins数组中

  1. var MyPlugin = require("./MyPlugin")
  2. module.exports = {
  3. plugins:[
  4. new MyPlugin()
  5. ]
  6. }
  • apply函数会在初始化阶段,创建好Compiler对象后运行。
  • compiler对象是在初始化阶段构建的,整个webpack打包期间只有一个compiler对象,后续完成打包工作的是compiler对象内部创建的compilation
  • apply方法会在创建好compiler对象后调用,并向方法传入一个compiler对象
  • compiler对象提供了大量的钩子函数,可以再apply方法中使用下面的代码注册钩子函数
  1. class MyPlugin{
  2. apply(compiler){
  3. compiler.hooks.事件名称.事件类型(name, function(compilation){
  4. //事件处理函数
  5. })
  6. }
  7. }

事件名称

即要监听的事件名,即钩子名,所有的钩子:https://www.webpackjs.com/api/compiler-hooks

事件类型

  • 这一部分使用的是 Tapable API,这个小型的库是一个专门用于钩子函数监听的库。
  • 它提供了一些事件类型:
    • tap:注册一个同步的钩子函数,函数运行完毕则表示事件处理结束
    • tapAsync:注册一个基于回调的异步的钩子函数,函数通过调用一个回调表示事件处理结束
    • tapPromise:注册一个基于Promise的异步的钩子函数,函数通过返回的Promise进入已决状态表示事件处理结束

处理函数

  • 处理函数有一个事件参数compilation

区分环境

开发中需要针对生产环境和开发环境分别书写webpack配置,为了更好的适应这种要求,webpack允许配置不仅可以是一个对象,还可以是一个函数

  1. module.exports = env => {
  2. return {
  3. //配置内容
  4. }
  5. }

在开始构建时,webpack如果发现配置是一个函数,会调用该函数,将函数返回的对象作为配置内容,因此,开发者可以根据不同的环境返回不同的对象,在调用webpack函数时,webpack会向函数传入一个参数env,该参数的值来自于webpack命令中给env指定的值,例如

  1. npx webpack --env abc # env: "abc"
  2. npx webpack --env.abc # env: {abc:true}
  3. npx webpack --env.abc=1 # env {abc:1}
  4. 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
  1. new HtmlWebpackPlugin({
  2. template: "./public/index.html",
  3. })
  • 复制静态资源copy-webpack-plugin
  1. new CopyWebpackPlugin([
  2. {from: '路径', to: '路径'}
  3. ])
  • 开发服务器webpack-dev-server
    • 常用配置:
    • port:配置监听端口
    • proxy:配置代理,常用于跨域访问
    • stats:配置控制台输出内容
  1. module.exports = {
  2. stats: {
  3. modules: false,
  4. colors: true
  5. }
  6. }

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,需要安装下面两个库:

  1. npm i -D @babel/core @babel/cli
  2. @babel/core: babel核心库,提供了编译所需的所有api
  3. @babel/cli: 提供了一个命令行工具,调用核心库的api完成编译

babel的使用

  1. 按文件编译
  2. babel 要编译的文件 -o 编辑结果文件
  3. 按目录编译
  4. babel 要编译的整个目录 -d 编译结果放置的目录

babel本身没有做任何事情,真正的编译要依托于babel插件和babel预设来完成的,babel预设和postcss预设含义一样,是多个插件的集合体,用于解决一些列常见的兼容问题