part 2 模块二 模块化开发与规范化标准

模块化开发

模块化演变过程

  1. 文件划分方式
  • 污染全局作用域
  • 命名冲突
  • 无法管理模块依赖关系
  1. 命名空间方式
  • 每个模块只暴露一个全局对象,所有模块成员都挂载到这个对象中
  • 模块成员可以被修改
  1. IIFE(立即执行函数表达式)
  • 为模块提供私有空间
  • 模块私有成员无法访问

模块化规范

  1. CommonJS规范(用于 node)
  • 一个文件就是一个模块
  • 每个模块都有独立的作用域
  • 通过 module.exports 导出成员
  • 通过 require 导入成员
  • 以同步模式加载模块
  1. AMD + Require.js
  • define 定义模块;require 载入模块
  • 使用起来相对复杂
  • 模块 Js 文件请求频繁
  1. Sea.js + CMD
  • CMD 规范类似于 Commonjs规范
  1. ES Modules
  • ES6提出的用于浏览器的标准模块化规范

ES Modules

  • 基本特性

    • 自动使用严格模式,忽略"use strict"
    • 每个 ESM 模块都是单独的私有作用域
    • ESM 通过 CORS 去请求外部 JS 模块
    • ESM 的 script 标签会延迟执行脚本
  • ES Modules 导出

    // import { name } 搭配使用
    export var name = "123"
    // import { fooName, helloFunction } 搭配使用
    export {
    	name as fooName,
    	helloFunction
    }
    // import { default as 任意除 default(或其余关键字) 之外变量名 } 搭配使用
    export { name as default }
    // import 任意除 default(或其余关键字) 之外变量名 搭配使用
    export default name
    
  • 导入导出的注意事项

     // 这里不是对象字面量,而是语法规则
     export { name, age }
     // 而 export default 导出的是字面量对象
     export default { name, age }
     // 这里不是解构字面量对象,与 export { name, age }搭配使用
     import { name, age }
    
    • 导入成员并不是复制副本,而是直接导入模块成员的引用地址,即 import 的变量与 export 的变量在内存中是同一块空间。一旦在 export 时成员修改了,import 得到的变量也会同步修改
    • import 模块成员变量是只读的,注意如果导入对象,对象的属性读写不受影响
  • 导入用法

    // 只能引入完成路径,不能忽略文件类型,不能忽略"/"
    // 只加载模块
    import "./modules.js"
    // 提取全部成员
    import * as all from "./modules.js"
    // 动态引入模块
    import("./modules.js").then(function(module) {
    	console.log(module)
    })
    // 提取默认成员和其他成员
    import { name, age, default as title} from "./modules.js"
    import title, { name, age } from "./modules.js"
    
  • 导出导入成员

    // 直接使用 export 导出导入的成员,就不用先 import 再 export
    export { defalut as Button } from "./button.js"
    export { Avatar } from "./avatar.js"
    
  • 浏览器环境的支持 polyfill

    // 只适合开发环境
    <script nomodule src="https://unpkg.com/promise-polyfill@8.1.3/dist/polyfill.min.js"></script>
    <script nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/babel-browser-build.js"></script>
    <script nomodule src="https://unpkg.com/browser-es-module-loader@0.4.1/dist/browser-es-module-loader.js"></script>
    
  • ES Modules in node.js

  1. 若想要 node.js 支持 ES Modules,文件后缀改成 .mjs
  2. 命令行执行 node --experimental-modules **.mjs
  • ES Modules in node.js 与 CommonJS 交互

    • ES Modules 中可以导入 CommonJS 模块
    • CommonJS 中不能导入 ES Modules 模块
    • CommonJS 始终只能导出一个默认成员
    • 注意 import 不是解构导出对象
  • ES Modules in node.js 与 CommonJS 差异

    • require、module、exports、__dirname、__filename 为 全局成员,但是 ES Modules 没有这些成员,但可以有其余属性代替
    • require、module、exports 通过 ES Modules 的 import、export 代替
    // __filename 和 __dirname 通过 import.meta.url 属性可以获取
    import { fileURLToPath } from "url"
    import { dirname } from "path"
    const __filename = fileURLToPath(import.meta.url)
    const __dirname = dirname(__filename)
    
  • ES Modules in node.js - 新版本进一步支持

    • Node v12 以上版本,在 package.json 添加 { type: “module” },将模块系统默认修改为 ES Modules,此时不需要 mjs 文件拓展名
    • 若在 { type: “module” } 继续使用 CommonJS,文件拓展名改为 .cjs
  • ES Modules in node.js - Babel 兼容方案

    • 借助 Babel 在低版本 node 环境下使用 ES Modules
    • 安装 @babel/node @babel/core @babel/preset-env @babel/plugin-transform-modules-commonjs
    • 新建 .babelrc 文件,两种配置如下
    // @babel/preset-env 插件集合
    {
    	"presets":  "@babel/preset-env"
    }
    // 或者使用单独的插件,效率更高,需要什么插件再引入什么插件
    {
      "plugins": [
        "@babel/plugin-transform-modules-commonjs"
      ]
    }
    

Webpack 打包

  • 模块打包工具由来

    • 模块化存在的问题
      • ES Modules 存在环境兼容
      • 模块文件过多,网络请求频繁
      • 所有的前端资源都需要模块化
    • 模块打包工具可解决问题
      • 新特性代码编译
      • 模块化 JS 打包
      • 支持不同类型的资源模块
  • Webpack 快速上手

    yarn init --yes
    yarn add webpack webapck-cli -d
    yarn webpack
    
  • Webpack 配置文件

    • 新建 webpack.config.js 文件
    const path = require("path")
    module.exports = {
    	entry: "./src/main.js",
    	output: {
    		filename: "bundle.js",
    		path: path.join(__dirname, "output")
    	}
    }
    
  • Webpack 工作模式
    在 webpack.config.js 文件下添加字段 mode 配置

    • production:默认模式,自动启用优化打包结果
    • development:自动优化打包速度,添加调试用到的辅助到代码中
    • none:运行最原始的打包,不会做任何额外处理
  • Webpack 打包结果运行界面

    • 入口函数接收模块参数
    • 先判断模块是否被加载过,有则从缓存中读,无则重新加载
    • 调用 r 函数给导出模块做标记
  • Webpack 资源模块加载

    • loader 是 Webpack 的核心特性
    • 借助于 Loder 就可以加载任何类型的资源
  • Webpack 导入资源模块

    • 入口文件应该是 JS 文件
    • 在 JS 文件中引入 CSS
      • 逻辑合理,JS 确实需要这些资源文件
      • 确保上线资源不缺失,都是必要的
  • Webpack 文件资源加载

    • output 添加属性 publicPath: "dist/"
    • 引入 file-loader,module 添加配置
    {
    	test: /.png$/,
    	use: "file-loader"
    }
    
  • Webpack URL 加载器

    • Data URLs 是一种特殊的 url 协议,可以直接用来表示一个文件
    • 小文件使用 Data URLs,减少请求次数
    • 大文件单独提取存放,提高加载速度
    • 使用 url-loader 也需要安装 file-loader
    module: {
    	rules: [
    		{
    	        test: /.png$/,
    	        use: {
    	          loader: "url-loader",
    	          options: {
    	            limit: 10 * 1024 // 10k
    	          }
            }
          }
    	]
    }
    
  • Webpack 常用加载器分类

    • 编译转换类:编译代码,如 css-loader、style-loader
    • 文件操作类:拷贝文件等,如 file-loader
    • 代码检查类:检查代码是否通过,不会更改生产代码,如 eslint-loader
  • Webpack 与 ES2015

    • Webpack 只是打包工具
    • 加载器用来编译转换代码
    • 使用 babel-loader、@babel/core、@babel/preset-env 编译 ES6 新特性为 ES5
    {
        test: /.js$/,
        use: {
          loader: "babel-loader", // 转换 js 平台
          options: {
            presets: ["@babel/preset-env"] // 插件集合编译具体新特性
          }
        }
      },
    
  • Webpack 加载资源的方式

    • 遵循 ES Modules 标准的 import 声明
    • 遵循 CommonJS 标准的 require 函数
    • 遵循 AMD 标准的 define 函数 和 require 函数
    • 样式代码中的 @import 指令和 url 函数
    • HTML 代码中图片标签的 src 函数
    • 引入 html-loader 处理 html 打包
    {
        test: /.html$/,
        use: {
          loader: "html-loader",
          options: {
            attrs: ["img:src", "a:href"]
          }
        }
      }
    
  • Webpack 核心工作原理

    1. 根据配置找到入口
    2. 顺着入口找到依赖文件,形成依赖树
    3. Webpack 递归依赖树找到每个节点对应资源的文件
    4. 交给对应的加载器加载文件
    5. 将加载结果放入到打包结果中
  • Webpack 开发一个 Loader

    • Loader 负责资源文件从输入到输出的转换
    • 对于同一个资源可以依次使用多个 loader
    • loader 具有工作管道特性
    // 新建 markdown-loader.js 文件
    const marked = require('marked')
    
    module.exports = source => {
      // console.log(source)
      // return 'console.log("hello ~")'
      const html = marked(source)
      // return html
      // return `module.exports = "${html}"`
      // return `export default ${JSON.stringify(html)}`
    
      // 返回 html 字符串交给下一个 loader 处理
      return html
    }
    
    {
       test: /.md$/,
       use: [
         'html-loader',
         './markdown-loader'
       ]
     }
    
  • Webpack 插件机制介绍

    • Loader 专注实现资源模块加载
    • Plugin 解决其他自动化工作
      • 清除 dist 目录
      • 拷贝静态文件至输出目录
      • 压缩输出代码
  • Webpack 自动清除输出目录插件

const CleanWebpackPlugin = require("clean-webpack-plugin")

module.exports = {
	module: .... // 此处省略
	plugins: [
		new CleanWebpackPlugin()
	]
}
  • Webpack 自动生成 HTML 插件
// src/index.html 模板文件
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Webpack</title>
</head>
<body>
  <div class="container">
  	// 会根据 plugin 配置渲染
    <h1><%= htmlWebpackPlugin.options.title %></h1>
  </div>
</body>
</html>
// webpack.config.js 文件
const HtmlWebpackPlugin = require("html-webpack-plugin")

module.exports = {
	module: .... // 此处省略
	plugins: [
		// 用于生成 index.html
		// 由于引入 bundle.js 路径变了,此文件配置已不需要 publicPath: "dist/"
		new HtmlWebpackPlugin(
			title: "Webpack Plugin Sample",
			meta: {
				viewport: "width=device-width"
			},
			template: "./src/index.html"
		),
		// 用于生成 about.html
		new HtmlWebpackPlugin(
			// 此值默认为 index.html
			filename: "about.html", 
		),
	]
}
  • Webpack 插件使用总结

    • 去 github 上可以查找到很多插件,插件的用法都是类似的
    • 复制 public 里面的所有文件
    // webpack.config.js 文件
    const CopyWebpackPlugin = require("copy-webpack-plugin")
    
    module.exports = {
    	module: .... // 此处省略
    	plugins: [
    		new CopyWebpackPlugin([
    			// 'public/**'
      			'public'
    		])
    	]
    }
    
  • Webpack 开发一个插件

    • 通过钩子机制实现,Webpack 在不同时期有不同的钩子,在钩子上挂载任务
    • 插件必须是一个函数或者是一个包含 apply 方法的对象,通常是后者
    • 接收一个 compiler 参数,通过这个对象注册钩子函数
    • 通过 tap 注册一个钩子函数,第一个参数为插件名,第二个参数为挂载到此钩子上的函数,接收 compilation (此次打包上下文)对象参数
    • 访问文件资源,逻辑处理固定返回 source 内容 和 size 文件大小
    class MyPlugin {
    	apply(compiler) {
    		console.log("MyPlugin启动")
    		compiler.hooks.emit.tap("MyPlugin", compilation => {
    			// compilation 此次打包上下文
    			console.log(compilation.assets)
    			for (const name in compilation.assets) {
    				console.log(name)
    				console.log(compilation.assets[name].source)
    				if (name.endsWith(".js")) {
    					const contents = compilation.assets[name].source()
    					const withoutComments = contents.replace(/\/\*\*+\*\//g, "")
    					compilation.assets[name] = {
    						source: () => withoutComments,
    						size: () => withoutComments.length
    					}
    				}
    			}
    		})
    	}
    }
    
  • Webpack 开发体验问题

    • 以 HTTP Server 运行
    • 自动编译 + 自动刷新
    • 提供 Source Map 支持
  • Webpack 自动编译 + 自动刷新浏览器

    • 执行命令 yarn webpack --watch,Webpack 以监视模式运行,等待文件变化再次工作
    • 再开启一个命令行终端,执行命令 browser-sync dist --files “**/*” 运行应用
    • 以上操作麻烦,效率低下
  • Webpack Dev Server

    • 执行命令 yarn webpack-dev-server --open 实现自动编译 + 自动刷新浏览器
    • 运行不生成 dist 文件,存储在内存中,HTTP Server直接读内存,效率更高
    • 开发阶段不要使用 CopyWebpackPlugin 这个插件
    // webpack.config.js
    devServer: {
    	contentBase: "./public" // 额外为开发服务器指定查找资源目录
    	proxy: {
          "/api": {
            // http://localhost:8080/api/users -> https://api.github.com/api/users
            target: "https://api.github.com",
            // http://localhost:8080/api/users -> https://api.github.com/users
            pathRewrite: {
              "^/api": ""
            },
            // 不能使用 localhost:8080 作为请求 GitHub 的主机名
            changeOrigin: true,
          }
        }
    }
    
  • SourceMap

    • 映射转换后的代码与源代码的关系
    • 解决了源代码与运行代码不一致所产生的问题
    • 若想在浏览器控制面板 SourceMap,比如可以在 jquery-3.4.1.min.js 最后一行添加
    //# sourceMappingURL=jquery-3.4.1.min.map
    
  • Webpack devtool 模式

    • eval:是否使用 eval 执行模块代码
    • cheap:Source Map 是否包含列信息
    • module:是否能够得到 Loader 处理之前的源代码
    • devtool: “eval” 不会生成 Soource Map,只能定位文件
    • devtool: “eval-source-map” 生成 Source Map,定位行列
    • devtool: “cheap-eval-source-map” 生成 Soource Map,定位行,但不定位列
    • devtool: “cheap-module-eval-source-map” 生成 Soource Map,定位行,能得到 Loader 处理之前的源代码
  • Webapck 选择 Source Map 模式

    • 开发环境:cheap-module-eval-source-map
    • 生产环境:none(不能定位到源文件,也看不见源代码)/nosources-source-map(能定位到源文件,但看不见源代码)
  • Webpack 自动刷新的问题

    • 问题核心:自动刷新导致的页面状态丢失
    • 解决思路:页面不刷新的前提下,模块也可以及时更新
  • Webapck HMR 体验

    • Hot Module Replacement 模块热更新
    • 只将修改的模块实时替换至应用中
  • Webapck 开启 HMR

const webpack = require("webpack")
devServer: {
	hot: true
},
plugins: {
	new webapck.hotModuleReplacementPlugin()
}
  • Webpack HMR 的疑问

    • 为什么样式文件的热更新开箱即用
      因为 style-loader 已经自动处理了样式的热更新
    • 凭什么样式可以自动处理
      因为样式有规律,但是 JS 毫无规律
    • 我的项目没有手动处理,JS 照样可以热更新
      框架已经处理热更新
  • Webpack 处理 JS、图片模块热更新
    使用 module.hot.accept(使用热更新的文件,处理函数)

    let hotEditor = editor
    module.hot.accept('./editor.js', () => {
      const value = hotEditor.innerHTML
      document.body.removeChild(hotEditor)
      hotEditor = createEditor()
      hotEditor.innerHTML = value
      document.body.appendChild(hotEditor)
    })
    module.hot.accept('./better.png', () => {
       img.src = background
       console.log(1)
     })
    
  • Webpack HMR 注意事项

    • 处理 HMR 的代码错误会导致自动刷新,可采取 hotOnly: true
    • 没开启 HMR 的情况下,HMR API 报错,可采取 if(module.hot) {}
    • 代码中多了一些与业务无关的代码
  • Webpack 生产环境优化
    模式 mode 为不同的工作环境创建不同的配置

  • Webpack 不同环境下的配置

    • 配置文件根据环境不同导出的配置
     module.exports = (env, argv) => {
      const config = {
        mode: 'development',
        entry: './src/main.js',
        output: {
          filename: 'js/bundle.js'
        },
        devtool: 'cheap-eval-module-source-map',
        devServer: {
          hot: true,
          contentBase: 'public'
        },
        module: {
          rules: [
            {
              test: /\.css$/,
              use: [
                'style-loader',
                'css-loader'
              ]
            },
            {
              test: /\.(png|jpe?g|gif)$/,
              use: {
                loader: 'file-loader',
                options: {
                  outputPath: 'img',
                  name: '[name].[ext]'
                }
              }
            }
          ]
        },
        plugins: [
          new HtmlWebpackPlugin({
            title: 'Webpack Tutorial',
            template: './src/index.html'
          }),
          new webpack.HotModuleReplacementPlugin()
        ]
      }
    
    // 当运行命令行 yarn webpack --env production 时则执行如下代码
      if (env === 'production') {
        config.mode = 'production'
        config.devtool = false
        config.plugins = [
          ...config.plugins,
          new CleanWebpackPlugin(),
          new CopyWebpackPlugin(['public'])
        ]
      }
    
      return config
    }
    
  • Webpack 不同环境下的配置文件

    • 创建三个文件,webpack.common.js、webpack.prod.js、webpack.dev.js
    • 使用 webpack-merge 合并公用部分
    • 使用 yarn webpack --config webpack.prod.js 打包生产环境
  • Webpack DefinePlugin
    为代码注入全局变量

    plugins: [
    	new webpack.definePlugin({
    		// 值要求的是一个代码片段
    		// 页面调用此值,直接使用 API_BASE_URL
    		// API_BASE_URL: "'https://api.example.com'"
    		API_BASE_URL: JSON.stringify()
    	})
    ]
    
  • Wepack Tree Shaking & scope Hoisting(合并代码)
    生产环境自动开启

    module.exports = {
      mode: 'none',
      entry: './src/index.js',
      output: {
        filename: 'bundle.js'
      },
      module: {
        rules: [
          {
            test: /\.js$/,
            use: {
              loader: 'babel-loader',
              options: {
                presets: [
                  // 如果 Babel 加载模块时已经转换了 ESM,则会导致 Tree Shaking 失效
                  // ['@babel/preset-env', { modules: 'commonjs' }]
                  // ['@babel/preset-env', { modules: false }]
                  // 也可以使用默认配置,也就是 auto,这样 babel-loader 会自动关闭 ESM 转换
                  ['@babel/preset-env', { modules: 'auto' }]
                ]
              }
            }
          }
        ]
      },
      optimization: { // 开启优化
       // Tree Shaking 模块只导出被使用的成员 相当于标记枯树枝
        usedExports: true,
        // Tree Shaking 压缩输出结果 相当于摇掉枯树枝
        minimize: true,
        // scope Hoisting 尽可能合并每一个模块到一个函数中
        concatenateModules: true
      }
    }
    
  • side-effects

    • 标注代码是否有副作用,给 Tree Shaking 提供更大的压缩空间
    • 要确保代码没有副作用,否则会误删代码
    • 副作用:模块执行时除了导出成员之外所作的事情
    • 一般用于 npm 包标记是否有副作用
    // webpack.config.js 配置
    optimization: {
    	// 开启功能
    	sideEffects: true
    }
    
    // package.json 配置
    // 标识代码没有副作用
    // "sideEffects": false
    // 标识某些文件有副作用
    "sideEffects": ["./src/extend.js", "*/.css"]
    
  • Code Splitting - 分包,按需加载

    • 多页面应用,多入口打包
    module.exports = {
    	entry: {
        	index: "./src/index.js",
        	album: "./src/album.js"
        },
        plugins: [
    	    new HtmlWebpackPlugin({
    	      title: 'Multi Entry',
    	      template: './src/index.html',
    	      filename: 'index.html',
    	      chunks: ["index"]
    	    }),
    	    new HtmlWebpackPlugin({
    	      title: 'Multi Entry',
    	      template: './src/album.html',
    	      filename: 'album.html',
    	      chunks: ["album"]
    	    }),
    	 ],
    	 optimization: {
    		// 自动提取所有公共模块到单独 bundle
    		chunks: "all"
    	}
    }
    
    • 动态导入
      • 需要用到的模块才加载
      • WebpackChunkName 魔法注释动态输出文件,如果名字相同会输出到同一文件
    if (hash === '#posts') {
        // mainElement.appendChild(posts())
        import(/* webpackChunkName: 'components' */'./posts/posts').then(({ default: posts }) => {
          mainElement.appendChild(posts())
        })
      } else if (hash === '#album') {
        // mainElement.appendChild(album())
        import(/* webpackChunkName: 'components' */'./album/album').then(({ default: album }) => {
          mainElement.appendChild(album())
        })
      }
    
  • mini-css-extract-plugin CSS 按需加载

const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const TerserWebpackPlugin = require('terser-webpack-plugin')

module.exports = {
  optimization: {
    minimizer: [
      // 压缩输出的 CSS 文件
      new TerserWebpackPlugin(),
      // 由于 minimizer 被覆盖,所以需要加上压缩输出的 JS 文件的功能
      new OptimizeCssAssetsWebpackPlugin()
    ]
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          // 'style-loader', // 将样式通过 style 标签注入
          MiniCssExtractPlugin.loader, // 将样式通过 link 引入
          'css-loader'
        ]
      }
    ]
  },
  plugins: [
  	// 提取 CSS 到单个文件,若 CSS 文件小于150KB,不建议提取
    new MiniCssExtractPlugin()
  ]
}
  • 输出文件名 Hash
output: {
  filename: '[name]-[contenthash:8].bundle.js'
},

其他打包工具

Rollup

仅仅是一个 ES Module 打包器
提供一个充分利用 ESM 各项特性的高效打包器

  • 快速上手

yarn add rollup --dev
yarn rollup ./src/index.js --format iife --file dist/bundle.js

  • 配置文件

    // 新建 rollup.config.js
    export default {
      input:"src/index.js",
      output: {
        format: "iife",
        file: "dist/bundle.js"
      }
    }
    

    执行 yarn rollup --config

  • 使用插件
    插件是 Rollup 唯一拓展途径

    import json from "rollup-plugin-json"
    
    export default {
      input:"src/index.js",
      output: {
        format: "iife",
        file: "dist/bundle.js"
      },
      plugins: [
      	// 可引入加载 json 文件
        json()
      ]
    }
    
  • Rollup 加载 npm 模块
    使用 rollup-plugin-node-resolve 插件加载 npm 模块,比如 lodash-es (ES Module 版本的 lodash,因为 Rollup 默认支持 ESM)

  • Rollup 加载 CommonJS 模块
    使用 rollup-plugin-commonjs 插件

  • Rollup 按需加载

    // 动态导入
    import("./logger").then(({ log }) => {
    	log("code splitting")
    })
    
    • 输入格式不能是 iife 只能是 amd
    export default {
    	input: "src/index.js",
    	output: {
    		dir: "dist",
    		format: "amd"
    	}
    }
    
  • Rollup 多入口打包

export default {
	// 数组和对象类型均可
	// input: ["src/index.js", "src/album.js"],
	input: {
		foo: "src/index.js",
		bar: "src/album.js"
	},
	output: {
		dir: "dist",
		format: "amd"
	}
}
  • Rollup 选用原则
    • 优点
      • 输出结果更加扁平
      • 自动移除未引用代码
      • 打包结果依然完全可读
    • 缺点
      • 加载非 ESM 的第三方模块比较复杂
      • 模块最终打包到一个函数中,无法实现 MHR
      • 浏览器环境中,代码拆分依赖 AMD 库
    • 总结
      • 开发应用程序可以使用 webpack
      • 开发框架/类库可以使用 Rollup
Rarcel
  • 零配置的前端应用打包器
  • 自动安装模块
  • 采用多进程打包工作,速度较快

yarn add parcel-bundle --dev
yarn parcel src/index.html // 以 index.html 为入口文件运行开发环境
yarn parcel build src/index.html // 打包生产环境

规范化标准

  • 规范化介绍

    • 为什么要有规范化标准
      • 软件开发需要多人协同
      • 不同开发者具有不同的编码习惯和爱好
      • 不同的喜好增加项目维护成本
      • 每个项目或者团队需要明确统一的标准
    • 哪里需要规范化标准
      • 代码、文档、甚至提交日志
      • 开发过程人为编写的成果物
      • 代码标准化规范最为重要
    • 实施规范化的方法
      • 编码前人为的标准约定
      • 通过工具实现 Lint
  • ESLint 介绍

    • 最为主流的 Javascript Lint 工具检测 JS 代码质量
    • ESLint 很容易统一开发者的代码风格
    • ESLint 可以帮助开发者提高编码能力
  • ESLint 快速入手

npm init --yes // 初始化项目
npm install eslint --save-dev // 安装 eslint
npx eslint --init // 初始化 eslint 配置
npx eslint ./src // 代码检查 src 文件
npx eslint ./src --fix // 自动修复 src 文件的有问题代码

  • ESLint 配置文件解析

    • env:标记代码运行的环境,例如 browser、node
    • extends: 继承共享配置,例如 standard 代表 github 上公用的配置
    • parserOptions:设置语法解析器配置,控制允许使用 ES 哪个版本
    • rules:检验规则的开启和设置
    • globals:代码可以使用的全局成员
  • ESLint 配置注释

    • 文档地址

    http://eslint.cn/docs/user-guide/configuring#configuring-rules

    • 示例
    // esline-disbale-line 为固定注释
    // no-template-curly-in-string 注释的内容类型
    const str1 = "${name} is a coder" // esline-disbale-line no-template-curly-in-string
    
  • ESLint 结合自动化工具

    • 完成相应的依赖安装
    • 完成 ESLint 模块安装
    • 完成 gulp-eslint 安装
    • npx eslint --init // 初始化生成 .eslintrs.js
    • 在 gulpfile.js 添加代码
    const script = () => {
    	return src("src/assets/scripts/*.js", {base: "src"})
    		.pipe(plugins.eslint())
    		.pipe(plugins.eslint.format())
    		.pipe(plugins.eslint.failAfterError()) // 发现错误则停止
    }
    
  • ESLint 结合 webpack

    • 完成相应的依赖、eslint模块、eslint-loader 模块安装
    • npx eslint --init
    • 在 webpack.config.js 中的 rules 添加如下配置,使得 eslint-loader 先于其他 loader 执行
    rules: [
    	...,
    	{
    		test: /.js$/,
    		exclude: /node_modules/,
    		use: "eslint-loader",
    		enfore: "pre", // 优先级高于其他 loader 执行
    	}
    ]
    
    • 在 .eslintrs.js 中的 extends 添加配置
    extends: [
    	"standard",
    	"plugin:react/recommended" // 为 react 项目添加检查 eslint 配置
    ]
    
  • 现代化项目集成

npm install @vue/cli -g
vue create vue-app // 选择 eslint 配置

  • Stylelint认识

    • 提供默认的代码检查规则
    • 提供 CLI 工具,快速调用
    • 通过插件支持 Sass Less PostCSS
    • 支持 Gulp 或 Webpack 集成
    • 安装:npm install stylelint --save-dev
    • 初始化:npx stylelint --init
    // 先安装开发依赖,新建 .stylelintrc.js 文件
    module.exports = {
    	extends: [
    		"stylelint-config-standard",
    		"stylelint-config-sass-guidelines"
    	]
    }
    
    • 运行:npx stylelint ./index.css
  • Prettier 的使用

    • 自动格式化工具

    npm install prettier --save-dev
    npx prettier . --write // 自动格式化当前所有文件

  • Git Hooks 工作机制

    • Git Hooks 也称为 git 钩子,每个钩子都对应一个任务
    • 通过 Shell 脚本可以编写钩子任务触发时要具体执行的操作
  • ESLint 结合 Git Hooks

npm install husky -D
npm install lint-staged -D

// 在 package.json 加上如下配置
"scripts": {
	"precommit": "lint-staged"
}
"husky": {
	"hooks": {
		"pre-commit": "npm run precommit"
	}
},
"lint-staged": {
	"*/.js": ["eslint", "git add"]
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值