模块化开发和规范化标准2

193 阅读7分钟
  1. 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: JSON.stringify('https://api.example.com')
        }) 
     ]}
    
  2. 模Webpack 体验和使用 Tree Shaking

    通常用于描述移除 JavaScript 上下文中的未引用代码
    Tree shaking 不是指某个配置选项,它是一组共能搭配使用后的优化效果,生产模式下自动开启
    webpack.config.js
    
    module.exports = {
      mode: 'none',
      entry: './src/index.js',
      output: {
        filename: 'bundle.js'
      },
      optimization: {
        // 模块只导出被使用的成员
        usedExports: true,    // 尽可能合并每一个模块到一个函数中,既提升了运行效率,又减少了代码的体积
        concatenateModules: true,    // 压缩输出结果    // minimize: true
      }
    }
    
  3. Webpack Tree Shaking 与 Babel

    webpack.config.js
    很多版本使用babel就会使tree shaking 失效,
    tree shaking 前提是有ES Modulkes
    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: {
        // 模块只导出被使用的成员 
       usedExports: true,
        // 尽可能合并每一个模块到一个函数中 
       // concatenateModules: true,
        // 压缩输出结果
        // minimize: true
      }
    }
    
  4. Webpack sideEffects 和注意事项

    sideEffects 副作用 : 模块执行时除了导出成员之外所作的事情
    sideEffects一般用于npm 包标记是否有副作用
    module.exports = {
      mode: 'none',
      entry: './src/index.js',
      output: {
        filename: 'bundle.js'
      },
      module: {
        rules: [
          {
            test: /\.css$/,
            use: [ 
             'style-loader',
              'css-loader' 
           ]
          }
        ]
      },
      optimization: {
        sideEffects: true,    // 模块只导出被使用的成员    
    // usedExports: true,    // 尽可能合并每一个模块到一个函数中    
    // concatenateModules: true,    // 压缩输出结果   
     // minimize: true, 
     }
    }
    确定代码真的没有副作用
    {  
    "name": "31-side-effects",
      "version": "0.1.0",
      "main": "index.js",
      "author": "zce <w@zce.me> (https://zce.me)",
      "license": "MIT",
      "scripts": {    "build": "webpack"  },
      "devDependencies": {
        "css-loader": "^3.2.0",
        "style-loader": "^1.0.0",
        "webpack": "^4.41.2", 
       "webpack-cli": "^3.3.9"
      },  
    "sideEffects": [
        "./src/extend.js", // extend.js 里有副作用的代码也打包进来
        "*.css"  
    ]}
    
  5. Webpack 代码分割

    代码过多,打包后打包到一起会显得包特别大,所以并不是每个模块在启动时都是必要的,分包,按需加载
    提高运行效率
    code splitting: 代码分包
    
     1. 多入口打包
     2. 动态导入
    
  6. Webpack 多入口打包

    最常用的打包规则: 一个页面对应一个打包入口,公共部门单独提取
    
    例子: 多个页面index.html,inedex.js 和album.html, album.js
    
    const { CleanWebpackPlugin } = require('clean-webpack-plugin')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    module.exports = {  
    mode: 'none',  
    entry: { 
    // 设置多入口   
     index: './src/index.js',
        album: './src/album.js'  
    }, 
     output: { 
       filename: '[name].bundle.js' // [name] 就会替换成入口设置的名字  
    },
      module: {
        rules: [ 
         {  
          test: /\.css$/,
            use: [
              'style-loader',
              'css-loader'
            ]
          }
        ]  },
      plugins: [ 
       new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
          title: 'Multi Entry',
          template: './src/index.html',
          filename: 'index.html',
          chunks: ['index'] // 使每个打包的html的只引用自己需要使用的js文件
        }), 
       new HtmlWebpackPlugin({ 
         title: 'Multi Entry',
          template: './src/album.html',
          filename: 'album.html',
          chunks: ['album'] 
       }) 
     ]}
    
  7. Webpack 提取公共模块

    不同入口中肯定会有公共模块
    
    const { CleanWebpackPlugin } = require('clean-webpack-plugin')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    module.exports = {
      mode: 'none',
      entry: {    
    index: './src/index.js',
        album: './src/album.js'
      },
      output: { 
       filename: '[name].bundle.js' 
     }, 
     optimization: {
        splitChunks: {
          // 自动提取所有公共模块到单独 bundle      chunks: 'all'    
    }  
    },
      module: {
        rules: [ 
         {  
          test: /\.css$/, 
           use: [  
            'style-loader',
              'css-loader' 
           ]
          } 
       ] 
     }, 
     plugins: [    
    new CleanWebpackPlugin(),
        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'] 
       })
      ]}
    
  8. Webpack 动态导入和魔法注释

    需要用到某个模块时,在加载这个模块,动态导入的模块会被自动分包
    index.js 入口文件
    // import posts from './posts/posts'
    // import album from './album/album'
    const render = () => {
      const hash = window.location.hash || '#posts'
      const mainElement = document.querySelector('.main') 
     mainElement.innerHTML = '' 
     if (hash === '#posts') {   
     // mainElement.appendChild(posts())
    // /* webpackChunkName: 'components' */ 这就可以给分包起一个名字 
      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())
        })
      }}render()window.addEventListener('hashchange', render)
    
  9. Webpack 提取css到单个文件MiniCssExtractPlugin和压缩文件OptimizeCssAssetsWebpackPlugin

    提取css到单个文件
    
    const { CleanWebpackPlugin } = require('clean-webpack-plugin')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const MiniCssExtractPlugin = require('mini-css-extract-plugin') // yarn add mini...
    // 压缩输出的css文件 yarn add optimize-css-assets-webpack-pluginconst 
    OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
    // 压缩js的插件
    const TerserWebpackPlugin = require('terser-webpack-plugin')
    module.exports = {  
    mode: 'none',
      entry: { 
       main: './src/index.js'
      }, 
     output: { 
       filename: '[name].bundle.js' 
     }, 
     optimization: { 
       minimizer: [ 
      // 压缩类的属性应该配置到minimizer里,进行统一控制;而不是在plugins里     
     new TerserWebpackPlugin(), // 压缩文件js不会被压缩,需要使用js的压缩配置进行压缩
          new OptimizeCssAssetsWebpackPlugin() // 压缩 
       ]  },
      module: { 
       rules: [
          {     
       test: /\.css$/, 
           use: [          // 'style-loader', // 将样式通过 style 标签注入页面 
             MiniCssExtractPlugin.loader, // 会单独存放在css文件中,通过link方式引入
              'css-loader'
            ]
          } 
       ]  }, 
     plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({ 
         title: 'Dynamic import',
          template: './src/index.html',
          filename: 'index.html'
        }), 
       new MiniCssExtractPlugin() // 添加安装的mini-css..的包
      ]}
    
  10.  Webpack 输出文件名 Hash

    substitutions 建议:生产模式下,文件名使用hash const { CleanWebpackPlugin } = require('clean-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin') const MiniCssExtractPlugin = require('mini-css-extract-plugin') const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin') const TerserWebpackPlugin = require('terser-webpack-plugin') module.exports = { mode: 'none', entry: { main: './src/index.js' }, output: { filename: '[name]-[contenthash:8].bundle.js' }, optimization: { minimizer: [ new TerserWebpackPlugin(), new OptimizeCssAssetsWebpackPlugin() ] }, module: { rules: [ { test: /.css$/, use: [ // 'style-loader', // 将样式通过 style 标签注入 MiniCssExtractPlugin.loader, 'css-loader' ] } ] }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ title: 'Dynamic import', template: './src/index.html', filename: 'index.html' }), new MiniCssExtractPlugin({ // filename: '[name]-[hash].bundle.css' 项目级别的hash,任何地方修改,hash值都会变化 // filename: '[name]-[chunkhash].bundle.css' 只要是同一路的打包,名称都是相同的 filename: '[name]-[contenthash:8].bundle.css' //文件级别的,:8 指定hash的字符长度 }) ]}

三、其他打包工具

  1. Rollup 概述

    es Module 打包器的一种,细小的代码打包为整块的代码
    rollup 与webpack 作用类似, rollup 更为小巧
    rollup 中并不支持类似HMR这种高级特性
    提供一个充分利用ESM各项特性的高校打包器
    
  2. Rollup 快速上手

    yarn init 
    yarn add rollup
    yarn rollup // 直接运行 会打印出所有帮助信息
    yarn rollup ./src/index.js --format iife //--format设置输出的设置; iife 自调用函数的格式
    // 此时的打包结果会显示在控制台中,下面这一步让其打包到dist文件里
    yarn rollup ./src/index.js --format iife --file dist/bundle.js
    
  3. Rollup 配置文件和配置插件

    // 设置配置文件,要放在根目录下
    rollup.config.js 配置文件
    
    import json from 'rollup-plugin-json' // yarn add rollup-plugin-jsonimport resolve from 'rollup-plugin-node-resolve' // yarn add rollup-plugin-node-resolveimport commonjs from 'rollup-plugin-commonjs'export default {  input: 'src/index.js', // 指定打包的入口文件路径  output: {              // 输出的相关配置    file: 'dist/bundle.js',  // 输出的文件名    format: 'iife'           // 输出的格式  },  plugins: [  // 使用插件    json(),//需要引用json文件的js里通过import{name}from '../xxx.json'方式直接引用json里参数    resolve(),    commonjs()  ]}
    
    src/index.js 入口文件
    // 导入模块成员
    // 使用esm的方式而不是直接使用lodash,是因为rollup默认只能够处理esm,如果直接导入commonjs模块
    //是不被支持的,需要先安装yarn add rollup-plugin-commonjs 后才能使用commonjs相关的模块import _ from 'lodash-es' import { log } from './logger'import messages from './messages'import { name, version } from '../package.json'import cjs from './cjs-module'  // => module.exports = {foo: 'bar'}// 使用模块成员const msg = messages.hilog(msg)log(name)log(version)log(_.camelCase('hello world'))log(cjs)
    
    // 1. yarn rollup --config要使用--config 运行,这样才能使用config文件里的配置
    // 2. rollup自身的内容就只是js模块的合并打包,如要额外的需求,则需要使用插件
    // 插件是rollup唯一的扩展方式
    // 3. rollup默认只能够按照文件路径的方式加载本地的文件模块,使用rollup-plugin-node-resolve
    // 就可以通过模块名称导入对应的模块,列如上面index.js文件 导入loadsh-es模块
    
  4. Rollup 代码拆分

    动态导入的方式
    src/index.js // 入口文件
    
    import('./logger').then(({ log }) => {
      log('code splitting~')}
    )
    // 配置文件
    export default {
      input: 'src/index.js',
      output: {
        // file: 'dist/bundle.js', // 拆分需要输出多个文件,就不能只打包到一个js文件里
        // format: 'iife' // 使用代码拆分的方式,就不能使用iife的输出格式,因为它是把代码合一起的
        dir: 'dist', //  输入多个文件使用dir方式 
       format: 'amd' // 使用amd的格式可以输出打包结果
      }}
    
  5. Rollup 多入口打包

    rollup.config.js 
    
    export default {  
    // input: ['src/index.js', 'src/album.js'],
      input: { // 多入口打包
        foo: 'src/index.js',
         bar: 'src/album.js'
      },
      output: {    dir: 'dist',    format: 'amd'  }}
    
    index.js
    
    import fetchApi from './fetch'
    import { log } from './logger'
    fetchApi('/posts').then(data => { 
     data.forEach(item => {
        log(item)
      })
    })
    // 支持多入口打包,对于不同文件公共的部分也会提取到单个文件中,作为单个bundle
    // 对于amd输出格式的,不能直接引用到页面上,需要通过引用adm的库去加载
      <!-- AMD 标准格式的输出 bundle 不能直接引用 -->
      <!-- <script src="foo.js"></script> -->
      <!-- 需要 Require.js 这样的库 -->
    <script src="https://unpkg.com/requirejs@2.3.6/require.js" data-main="foo.js"></script>
    
  6. Rollup 选用原则

    Rollup 优点:
      1. 输出结果更加扁平
      2. 自动移除未引用代码
      3. 打包结果依然完全可读
    缺点:
      1. 加载非ESM得第三方模块比较复杂
      2. 模块最终都被打包到一个函数中,无法实现HMR
      3. 浏览器环境中,代码拆分功能依赖AMD库
    
    
    开发大的应用程序,缺点会比较明显
    开发一个javascript框架或者类库,优点就比较明显
    
  7. Parcel

    零配置的前端应用打包器,
    优点:多进程同时工作,构建速度快
    1. yarn init
    2. yarn add parcel-bundler --dev 安装parcel    //谐音:派so
    3. 新建 src/index.html  //这将是后面打包的入口文件 
    // 官方推荐,因为它是应用运行在浏览器端的入口文件index.html
    index.html
    
    <body>  <script src="main.js"></script></body>
    
    
    
    main.js
    
    // 直接引入会自动安装依赖
    // import $ from 'jquery'
    import foo from './foo' 
    import './style.css'
    import logo from './zce.png'foo.bar()
    import('jquery').then($ => { 
     // 动态导入的方式  
    $(document.body).append('<h1>Hello Parcel</h1>')
      $(document.body).append(`<img src="${logo}" />`)})
    if (module.hot) { 
    // 热更新,当前模块或者当前模块依赖的文件有修改,才会自动更新
      module.hot.accept(() => { 
       console.log('hmr')  
    })
    }
    yarn parcel src/index.html // 运行开发模式打包
    
    yarn parcel build src/index.html // 以生产方式进行打包
    

四、规范化标准

  1. 规范化介绍

  2. ESLint 介绍

    最为主流的jslint工具监测js代码质量
    容易统一开发者的编码风格
    帮助开发者提升编码能力
    
  3. ESLint 安装

    1. 初始化项目 npm init
    2. 安装ESlint 模块为开发依赖 npm install eslint --save-dev
    3. cli命令验证安装结果cd .\node_modules\ ,cd .\.bin\, .\eslint  或者npx eslint都能查看
    
  4. ESLint 快速上手

    npx eslint --init 
    一、会有三个选项
    1. To check syntax only // 只检查语法错误
    2. To check syntax and find problems // 检查语法并发现问题
    // 3. 检查语法、发现问题并强制执行代码样式3. To check syntax, find problems, and enforce code style 
    
    二、模块化采用哪种方式 
    三、用的是哪个框架 
    四、是否用到typescript
    五、运行在哪个环境中
    六、怎样定义代码风格
    七、配置文件以何种格式存放/ js
    八、会问是否安装插件/ 安装
    npx eslint .\01.js --fix
    
  5. ESLint 配置文件解析

    .eslintrc.js
    
    module.exports = {
      env: {     // 标记代码最终的运行环境,根据环境信息判断全局成员是否是可用的
        browser: true, // 代码运行在浏览器中
        es2020: true  
    },
      extends: [  // 集成一些共享配置 
       'standard'  
    ],
      parserOptions: {
        ecmaVersion: 11  // 控制es6使用的版本
      }, 
     rules: { // 配置效验规则的开启和关闭
     'no-alert': "error" // off 关闭,on 开启 ,error 报出错误// 使用 alert()会报错
      }globals: { // 代码中可以使用的全局成员    "jQuery": "readonly"  }}
    
  6. ESLint 配置注释

    将配置通过注释的方式写在脚本文件中,然后执行代码效验
    1. const str1 = "${name} is a coder"
     // eslint-disable-line no-template-curly-in-string
     2. console.log(str1)这样就可以禁用第一行的eslint校验
    
  7. ESLint 结合自动化工具

    优点: 集成之后,eslint 一定会工作; 与项目统一,管理更加方便
    
  8. ESLint 结合 Webpack 配置

  9. 现代化项目集成 ESlint

    vue create wx-vue-app // 创建vue项目
    
  10.  ESLint 检查 TypeScript

    .eslintrc.js

    module.exports = { env: { browser: true, es2020: true }, extends: [ 'standard' ], parser: '@typescript-eslint/parser', // 语法解析器 parserOptions: { ecmaVersion: 11 }, plugins: [ '@typescript-eslint' ], rules: { }}

  11. Stylelint 认识

    提供默认的代码检查规则 提供cli工具,快速调用 通过插件支持sass less postcss 支持gulp 或者webpack集成 安装 npm install stylelint -D npx stylelint ./index.css 快速找到stylelint 创建 .stylelintrc.js

    .stylelintrc.js

    module.exports = { // 如果有多个,可以使用数组 extends: 'stylelint-config-standard' }

  12.  Prettier 的使用

    通用前端代码格式化工具

    npm install perttier -D

    npx prettier style.css --write //将对应的css代码给直接格式化 npx prettier . --write // 将所有文件代码格式化

  13. Git Hooks 工作机制

    通过git Hooks在代码提交前强制lint 介绍: 1. git Hook 也称之为git钩子,每个钩子都对应一个任务 2. 通过shell 脚本可以编写钩子任务触发时要具体执行的操作

    在本地git文件中 hooks文件中找到 perpare-commit.sample的文件进行修改

  14.  ESLint 结合 Git Hooks

    Husky 可以实现git Hooks的使用需求 安装 npm install husky -D

    padkage.json里添加和修改配置 "scripts": { "test": "eslint ./index.js" }

    "husky":{ "hooks": { "pre-commit": "npm run test" } }

    git add . git commit -m '测试' // 代码就会lint操作

    1. lint-staged // commit之后可以继续检查格式化

    2.1 安装 npm install lint-staged -d 2.2 配置具体配置

    package.json里配置 "scripts": { "test": "eslint ./index.js", "percommit": "lint-staged" }"husky":{ "hooks": { "pre-commit": "npm run percommit" } } "lint-staged": { "*.js": { "eslint", "git add" } }

    git add . git commit -m 'zzz' //就会强行lint