前端工程化

模块化开发

模块化概述

模块化是一种主流的组织方式,它经过把咱们的复杂代码按照功能的不一样划分为不一样的模块,单独的维护这种方式,提升咱们的开发效率,下降维护成本。javascript

模块化只是思想,不包含具体的实现。css

模块化演变过程

  • Stage1-文件划分方式:基于文件划分的方式
    • 污染全局做用域
    • 命名冲突问题
    • 没法管理模块依赖关系
    • 早期模块化彻底依靠约定
  • Stage2-命名空间方式:每一个模块只暴露一个全局对象,因此模块成员都挂载到这个对象中
    • 没有私有空间,模块成员能够被外部修改
    • 模块之间的依赖关系没有获得解决
  • Stage3-IIFE:采用当即执行函数表达式为模块提供私有空间
    • 私有成员的概念:须要暴露给外部的成员经过挂载到全局对象上去实现
    • 利用自执行函数的参数做为依赖声明去使用,每一个模块之间的依赖关系变得明显,利于后期维护

模块化规范的出现

  • CommenJS规范(NodeJS中提出的一套标准,NodeJS全部的模块代码必需要遵循CommenJS规范)
    • 一个文件就是一个模块
    • 每一个模块都有单独的做用域
    • 经过module.exports导出成员
    • 经过require函数载入模块
    • CommonJS是以同步模式加载模块
  • AMD(异步的模块定义规范)
    • Require.js实现了AMD规范,另外它自己也是一个强大的模块加载器
    • 目前绝大多数第三方库都支持AMD规范
    • AMD使用起来相对复杂
    • 模块JS文件请求频繁
  • CMD(通用的模块定义规范)
    • Sea.js+CMD

模块化标准规范

  • CommonJS in Node.jshtml

    • Node内置的模块系统,没有任何的环境问题
  • Es Modules in Browers前端

    • 最主流的前端模块化规范
    • 绝大数的浏览器都支持Es Modules这个特性(原生支持意味着能够直接使用这个特性)

ES Modules特性

  • 经过给script添加type=module的熟悉,就能够以ES Module的标准执行其中的JS代码
  • 自动采用严格模式,忽略use strict。
  • 每一个ES Module都是运行在单独的私有做用域中
  • ESM是经过CORS的方式请求外部JS模块
  • ESM的Script标签会延迟执行脚本,不会阻碍页面元素的显示

ES Modules导入导出的注意事项

  • 导出的成员并非一个字面量对象,语法和字面量对象很像。导入的语法很ES6的结构很像,可是它不是一个结构。
  • ES Modules模块的导出的不是成员的值,而是这个值存放的地址,在外部拿到的这个成员会受当前内部模块修改的影响
  • 在外部导入一个模块的成员只是一个只读的成员,并不能去修改它们

ES Modules导入用法和注意事项

  • 不能省略.js的扩展名,和CommonJS是有区别的
  • 不能像CommonJS那样载入目录的方式载入index.js,须要提供完整的路径
  • 后期可使用打包工具打包咱们的模块时能够省略扩展名和省略index.js等操做
  • 相对路径下的./在ESModule是不能省略的,否则会认为是在加载第三方的模块,和Common相同。
  • 可使用完整的URL加载模块
  • 可使用*号方式把全部的成员所有提取出来,可使用as的方式把全部导出的成员放入到一个对象当中
  • 不能嵌套在if和函数当中
  • import函数能够动态导入模块,这个函数返回的是一个Promise,当这个模块加载完成以后会自动执行then当中所指定的回调函数,模块的对象能够经过参数去拿到

ES Modules浏览器环境Polyfill

  • IE不兼容ES Modules,能够借助编译工具将ES6的代码编译成ES5的方式才能正常工做
  • Polyfile可让浏览器直接去支持ES Modules当中绝大多少的特性
  • npm的模块可使用unpkg.com这个网站提供的cdn服务去拿到它下面全部的js文件。
    • unpkg.com/browser-es-…
    • 工做原理:经过es-module-loader读取出来交给babel去转换,从而让咱们的代码能够正常工做
  • promise-polyfill可让浏览器支持Promise(浏览器支持能够忽略)
  • 支持ESM的浏览器用了polyfill会被执行两次,元素是浏览器自己支持被执行了一次,而后es-modules的polyfill也会执行一次,能够借助script标签的新属性nomodule去解决
    • nomodule标签只会在不支持esmodules的浏览器环境中工做
  • 总结:
    • 这种兼容ES Modules的方式只适合咱们本地测试或者开发阶段
    • 生成阶段千万不要去用,它的原理是在运行阶段动态的去解析脚本,效率太低
    • 生成阶段应该预先去把这些代码编译出来,让它们在浏览器中直接运行工做

ES Modules in Node.js:支持状况

  • Node8.5版本事后,能够原生的使用ESM去编写咱们的代码,还处于实验阶段
  • CommonJS规范与ESM规范差距较大,目前仍是处于过渡状态
  • 可使用node --experimental-modules xxx.mjs去启用这个试验特性执行文件
  • 内置模块兼容了ESM的提取成员方式
  • 第三方模块都是导出默认成员

ES Modules in Node.js:与CommonJS模块交互

  • CommonJS模块始终只会导出一个默认成员,ESM不能直接提取成员,注意import不是解构导出对象
  • Node环境当中不能在CommonJS模块中经过require载入ES Modules
  • 总结:
    • ES Modules中能够导入CommonJS模块
    • CommonJS中不能导入ES Modules模块
    • CommonJS始终只会导出一个默认成员
    • 注意import不是解构导出对象,它只是一个固定的用法,去提取导出模块当中的命名成员

ES Modules in Node.js:与CommonJS之间的差别

  • CommonJSvue

    • require:加载模块函数
    • module:模块对象
    • exports:导出对象别名
    • __filename:当前文件的绝对路径
    • __dirname: 当前文件所在的目录
  • ES Modulesjava

    • import: 加载模块函数
    • export: 导出对象别名
    • import.meta.url :当前所工做文件的文件URL地址
    • 经过url模块中的fileURLToPath方法能够获得__filename当前文件的绝对路径
    • 经过path模块中的dirname方法获得__dirname当前文件所在的目录
  • ESM中没有CommonJS中的那些模块全局变量node

ES Modules in Node.js:新版本进一步支持ESM

  • 能够在package.json下添加type字段,将type字段设置为module,这样咱们项目下全部的js文件都会以ESM规范去工做,就不用将扩展名改为mjs了。
  • 将type设置成module以后,就没法直接使用CommonJS规范了,这时候想要使用CommonJS的话须要将文件的扩展名改为.cjs

ES Modules in Node.js:Babel兼容方案

  • babel是一款主流的JavaScript编译器,它能够用来将咱们使用了一些新特性的代码编译成当前环境所支持的代码

Webpack打包

模块打包工具的由来

  • 模块化解决了咱们在代码开发当中的代码组织问题,随着咱们引入模块化,咱们的应用会产生新的问题。
    • ES Modules存在环境兼容问题
    • 模块文件过多,网络请求频繁
    • 全部的前端资源都须要模块化
      • 随着应用的日益复杂,html/css文件也会面临这些问题
    • 开发阶段包含新特性的代码转换为绝大多数环境支持的代码,解决环境兼容的问题
    • 将散落的模块文件打包在一块儿,解决模块文件过多,请求频繁的问题
    • 支持不一样类型的资源模块(.js/.css/.scss/.hbs/.png/.ts)

模块打包工具概要

  • webpack
    • 模块打包器(Module bundler)
      • 将零散的模块代码打包到同一个js文件当中
    • 模块加载器(Loader)
      • 在代码中有环境兼容问题的代码经过模块加载器进行编译转换
    • 代码拆分(Code Splitting)
      • 将应用当中全部的代码按照咱们的须要去打包,不用担忧把全部的代码所有打包在一块儿产生的文件过大的问题,能够把应用程序初次运行的时候所必须的一些模块打包在一块儿,其它的模块单独的进行存放,实际须要使用的时候去异步加载这些模块。
    • 资源模块(Assets Module)
      • 支持以模块化的方式去载入任意类型的资源文件
    • 打包工具解决的是前端总体的模块化,并不单指JavaScript模块化

快速上手

webpack做为目前最主流的前端模块打包器,提供了一整套前端项目模块化方案。react

  • 安装:yarn add webpack webpack-cli --dev
  • 编译:yarn webpack

配置文件

webpack4之后的版本它支持零配置的方式直接启动打包,打包过程会按照约定src/index.js=>dist/main.jsjquery

  • 在项目根目录下添加一个webpack.config.js文件,这个文件是运行在Node环境中的,咱们使用CommonJS规范
const path = require('path')

module.exports = {
    //去指定webpack打包入口文件的路径
    entry: './src/main.js',
    //设置输出文件的配置
    output: {
        //输出文件的名称
        filename: 'bundle.js',
        //输出文件的路径(绝对路径)
        path: path.join(__dirname, 'dist')
    }
}
复制代码

工做模式

webpack4新增工做模式作法,这种用法大大的简化了webpack配置的复杂程度,能够把它理解成针对于不一样环境的几组预设配置webpack

  • 生产模式:yarn-webpack (默认webpack --mode production)
    • 自动启用优化插件,将咱们的代码进行压缩
  • 开发模式:webpack --mode development
    • 自动优化打包的速度,添加一些调试过程当中须要的辅助到咱们代码当中
  • none模式:webpack --mode none
    • 进行最原始的打包,不会进行任何额外的处理
module.exports = {
    //工做模式
    mode: 'development'
}
复制代码

打包结果运行原理

总体生成的代码是一个当即执行的函数,这个函数接受一个modules的参数,调用时传入了一个数组

展开这个数组,数组中的每个元素都是一个参数列表相同的函数,这里的函数对于的是源代码当中的模块,每个模块最终都会包裹到这些函数当中,从而实现模块的私有做用域

资源模块加载

Loader是Webpack的核心特性,借助于Loader就能够加载任何类型的资源。

以加载css为例,首先安装css-loader来转换css文件,在安装style-loader将css-loader转换事后的结果经过style标签的形式添加到页面上,webpack配置以下:

// 配置对象
    module: {
        //其余资源模块加载规则
        rules: [{
            //匹配打包过程当中遇到的文件路径
            test: /.css$/,
            //匹配文件打包过程当中用的loader 配置了多个loader执行顺序是从后往前执行
            use: [
                'style-loader', //把css-loader转换后的结果经过style标签的形式添加到页面上
                'css-loader' //处理css文件的加载器
            ]
        }]
    }
复制代码

导入资源模块

webpack的打包入口通常是javascript文件,通常打包入口是应用程序的运行入口,目前而言,前端应用中的业务是由JavaScript来驱动的。

import './heading.css'

export default () => {
    const element = document.createElement('h2')

    element.textContent = 'Hello world'
    element.classList.add('heading')
    element.addEventListener('click', () => {
        alert('Hello webpack')
    })

    return element
}
复制代码
  • 传统的作法当中咱们将样式和行为单独分开和引入,webpack建议咱们在js文件当中载入css,咱们编写代码的过程中根据代码须要动态导入资源文件,真正须要资源的不是应用,而是此时正在编写的代码
  • JavaScript驱动整个前端应用,在实现业务功能的过程中可能须要图片或者样式等资源文件,若是创建了这个依赖关键,逻辑合理,JS确实须要这些资源文件,确保上线资源不缺失,都是必要的
  • 学习新事物不是学会它的全部用法你就能提升,由于这些东西照着文章谁均可以,要搞清楚它为何这样设计,基本上算是出道了
    • 新事物的思想才是突破点

加载器

文件加载器

  • file-loader 处理文件加载器

  • 文件加载器的工做过程

    • webpack在打包时遇到咱们的图片文件,根据咱们配置文件当中的配置匹配到对应的文件加载器,此时文件加载器开始工做,它先将咱们导入的文件拷贝到输出目录,而后将输出目录的路径做为当前模块的返回值返回,这样对于咱们的应用来讲所须要的资源就被发布出来了,同时咱们能够经过模块的导出成员拿到咱们资源的访问路径

    • {
      	test: /.png$/,
      	use: 'file-loader' //文件加载器
      }
      复制代码

URI加载器

  • Data URIs是一种当前URL就能表示文件内容的方式,这种URL中的文本就已经包含了文件内容,咱们在使用这种URL的时候就不会去发送任何的HTTP请求

  • url-loader Data URI加载器

    • {
      	test: /.png$/,
      	use: 'url-loader', //Data URLs加载器
      	//配置选项
      	options: {
      		limit: 10 * 1024 // 只将10kb如下的文件用url-loader处理
      	}
      }
      复制代码
  • 最佳实践

    • 小文件使用Data URLs,减小请求次数
    • 大文件单独提早存放,提升加载速度
    • 超出10KB文件单独提取存放
    • 小于10KB文件转换为Data URLs嵌入代码
  • 注意事项:对于超出大小的文件url loader会去调用file loader,因此仍是要安装fileloader

经常使用分类加载器

webpack的资源加载器相似生活当中工厂里面的生产车间,它是用来处理和加工打包过程看成的资源文件

  • 编译转换类
    • 这种类型的loader会把咱们的模块转换成JavaScript的代码
  • 文件操做类
    • 文件操做类型的加载器会把咱们的资源模块拷贝到输出目录,同时将文件的访问路径向外导出
  • 代码检查类
    • 对咱们所加载到的资源文件进行校验,它的目的统一咱们的代码风格,提升咱们的代码质量

Webpack与ES 2015

因为webpack默认就能处理咱们代码当中的import/export,因此很天然的有人认为webpack会自动编译es6的代码,由于模块打包须要,因此处理import/export,除此以外并不能处理代码当中其余的es6特性,若是咱们须要在打包过程看成处理其余es6特性的转换,咱们须要为js文件添加一个额外的编译性loader

  • babel-loader,一个编译型loader,用来处理es6特性的转换

    • {
          test: /.js$/,
              use: {
                  loader: 'babel-loader', //处理es6代码看成的新特性
                      options: {
                          //babel只是一个转换js代码的平台,在平台转换过程当中须要额外的插件
                          presets: ['@babel/preset-env'] 
                      }
              }
      }
      复制代码
  • webpack只是打包工具

  • 加载器能够用来编译转换代码

加载资源的方式

  • 遵循ES Modules标准规范的import声明
  • 遵循CommonJS标准的Require函数
  • 遵循AMD标准的define函数和require函数
  • Loader加载非JavaScript也会触发资源加载
    • 样式代码中的@import指令和url函数
    • HTML代码中图片标签的src属性
  • *样式代码中的@import指令和url函数
  • *HTML代码中图片标签的src属性
{
    test: /.html$/,
    use: {
            loader: 'html-loader', //html解析器
            options: {
            // html加载的时候对页面上的一些属性作一些额外处理
            attrs: [
                'img:src', //默认
                'a:href'
            ]
        }
    }
}
复制代码

核心工做原理

  • 在咱们的项目当中通常都会散落着各类各样的代码和资源文件,webpack会根据咱们的配置找到其中的一个文件做为打包的入口,而后顺着为咱们的入口文件当中的代码,根据咱们代码中出现的import/require以内的语句解析推断出来这个文件所依赖的资源模块,分别去解析每一个资源模块的资源依赖,最后会造成整个项目中全部文件之间依赖关系的依赖树,有了这个依赖树事后会递归这个依赖树,找到每一个节点所对应的资源文件,根据咱们配置文件当中的rules属性去找到这个模块所对应的加载器,而后交给对应的加载器去加载这个模块,最后将加载到的结果放入到bundle.js(配置的输出文件路径)当中,从而实现整个项目的打包
  • Loader机制是Webpack的核心,若是没有loader就没有办法去实现各类资源文件的加载,对于webpack来讲就只是一个打包或者合并代码的工具了

开发一个Loader

  • markdown-loader 在代码当中直接导入markdown文件

    • 输入就是资源文件的内容
    • 输出是处理完成以后的结果
  • 实现方式

    const marked = require('marked') //markdown解析模块
    
    module.exports = source => {
        const html = marked(source)
            //返回的类型必定要是js代码
            // return 'console.log("hello ~")'
            //直接拼接html当中存在的换行符和内部的引号拼接在一块儿可能引发语法错误
            // return `module.exports="${html}"` 
            //CommonJS方式导出字符串
            // return `module.exports=${JSON.stringify(html)}`
            //ES Modules方式导出
            // return `export default ${JSON.stringify(html)}`
    
        //返回html字符串交给下一个loader处理
        return html
    }
    复制代码
    {
         test: /.md$/,
         use: [
             'html-loader',
             './markdown-loader'
         ]
     }
    复制代码
  • 工做原理

    • Loader负责资源文件从输入到输出的转换
    • Loader是一种管道的概念,咱们能够将咱们这次Loader的结果交给下一个Loader处理
    • 对于同一个资源可一次使用多个Loader
      • css-loader=>style-loader

插件机制介绍

  • 插件机制是webpack另一个核心特性,目的是为了加强webpack在项目自动化的能力
  • loader专一实现资源模块的加载,从而实现总体项目的打包
  • plugin解决其余自动化工做
    • 清除dist目录
    • 拷贝静态文件至输出目录
    • 压缩输出代码
  • webpack+plugin实现了大多前端工程化绝大多数常常用到的部分

自动清除输出目录插件

  • clean-webpack-plugin 自动清除输出目录
//配置插件
plugins: [
    //清理输出目录
    new CleanWebpackPlugin()
]
复制代码

自动生成HTML插件

  • html-webpack-plugin 自动生成使用bundle.js的HTML
//配置插件
plugins: [
    //清理输出目录
    new CleanWebpackPlugin(),
    //自动生成index.html
    new HtmlWebpackPlugin({
        title: 'Webpack Plugin Sample',
        meta: {
        	viewport: 'width=device-width'
        },
        template: './index.html'
    }),
    //用于生成about.html
    new HtmlWebpackPlugin({
        filename: 'about.html'
    })
]
复制代码

插件使用总结

  • copy-webpack-plugin 将文件拷贝到输出目录
  • 社区当中提供了成百上千的插件,咱们并不须要所有认识,当咱们有特殊需求时,咱们只须要提取需求当中的关键词,而后去github上搜索它们,虽然每一个插件的做业不经相同,可是它们的用法上几乎相似。

开发一个插件

  • 相比于loader,plugin拥有更宽的能力范围,loader只是加载模块的环境去工做,plugin的工做范围几乎能够触及到webpack工做的每个环节
  • plugin经过钩子机制实现
  • webpack要求plugin必须是一个函数或者是一个包含apply方法的对象
  • 总结
    • 插件是经过在生命周期的钩子中挂载函数实现扩展
class MyPlugin {
    apply(compiler) {
        console.log('My Plugin 启动')
        compiler.hooks.emit.tap('MyPlugin', compilation => {
            //compilation能够理解为这次打包的上下文
            for (const name in compilation.assets) {
                // console.log(compilation.assets[name].source())
                //判断是不是js文件
                if (name.endsWith('.js')) {
                    //获取文件的内容
                    const contents = compilation.assets[name].source()
                        //将注释替换成空
                    const withoutComments = contents.replace(/\/\*\*+\*\//g, '')
                        //将最终结果覆盖原有的内容当中
                    compilation.assets[name] = {
                        //返回新的内容
                        source: () => withoutComments,
                        //返回内容大小,这个方式是webpack内部要求必须的方法
                        size: () => withoutComments.length
                    }
                }
            }
        })
    }
}
复制代码
//配置插件
plugins: [
    //自定义插件 删除生成的js文件当中的注释
    new MyPlugin()
]
复制代码

开发体验问题

  • 以HTTP Server运行,而不是以文件的方式进行预览
  • 自动编译+自动刷新
  • 提供Source Map支持

自动编译

  • watch工做模式
    • 监听文件变化,自动从新打包
    • yarn webpack --watch

自动刷新浏览器

  • BrowserSync 这个工具能够帮咱们实现自动刷新的功能
    • 须要同时使用两个工具
    • 开发效率上下降,开发过程中webpack会不断写入磁盘,browserSync又从磁盘中读取出来,这个过程中一次就会多出两步的读写操做

Webpack Dev Server

  • Webpack Dev Server是Webpack官方推出的一个开发工具
  • 提供用于开发的HTTP Server
  • 集成自动编译和自动刷新浏览器等功能

静态资源访问

  • Webpack Dev Server默认会将构建结果输出的文件所有做为开发服务器的资源文件,只要经过webpack打包输出的文件都能被访问到,但若是还有一些静态资源也须要做为开发资源被访问的化,须要额外的告诉webpack dev server
  • contentBase 额外为开发服务器指定查找资源目录

代理API

  • 跨域资源共享(CORS),使用CORS的前提是API必须支持,并非任何状况下API都应该支持

  • 同源部署(域名端口协议一致)

  • 开发阶段接口跨域

    • 开发服务器中配置代理服务,把接口服务代理到本地开发服务的地址
  • webpack dev server支持配置代理

  • 用法

    • 目标:将Github API代理到开发服务器
    // webpack dev server的配置选项
    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
                        //代理路径重写
                        publicPath: {
                            '^/api': ''
                        },
                        //不能使用 localhost:8080 做为请求 github 的主机名
                        changeOrigin: true //以实际代理请求的主机名去请求
                }
            }
    }
    复制代码

Source Map

  • 运行的代码与源代码之间彻底不一样,若是须要调试应用,错误信息没法定位,调试和报错都是基于运行代码
  • Sourece Map(源代码地图),能够经过SourceMap文件逆向解析源代码
  • Sourece Map解决了源代码与运行代码不一致所产生的问题

配置Source Map

//配置开发过程当中的辅助工具
devtool: 'source-map'
复制代码
  • 截止到目前,webpack支持12种不一样的方式,每种方式的效率和效果各不相同
devtool 构建速度 从新构建速度 生产环境 品质(quality)
(none) 很是快速 很是快速 yes 打包后的代码
eval 很是快速 很是快速 no 生成后的代码
eval-cheap-source-map 比较快 快速 no 转换过的代码(仅限行)
eval-cheap-module-source-map 中等 快速 no 原始源代码(仅限行)
eval-source-map 比较快 no 原始源代码
eval-nosources-source-map
eval-nosources-cheap-source-map
eval-nosources-cheap-module-source-map
cheap-source-map 比较快 中等 yes 转换过的代码(仅限行)
cheap-module-source-map 中等 比较慢 yes 原始源代码(仅限行)
inline-cheap-source-map 比较快 中等 no 转换过的代码(仅限行)
inline-cheap-module-source-map 中等 比较慢 no 原始源代码(仅限行)
inline-source-map no 原始源代码
inline-nosources-source-map
inline-nosources-cheap-source-map
inline-nosources-cheap-module-source-map
source-map yes 原始源代码
hidden-source-map yes 原始源代码
hidden-nosources-source-map
hidden-nosources-cheap-source-map
hidden-nosources-cheap-module-source-map
hidden-cheap-source-map
hidden-cheap-module-source-map
nosources-source-map yes 无源代码内容
nosources-cheap-source-map
nosources-cheap-module-source-map

eval模式的Source Map

  • eval是js当中的一个函数,它能够运行字符串当中的js代码,默认状况下运行在临时的虚拟机环境当中
  • 不会生成source map文件,构建速度最快,只能定位源代码文件的名称,而不知道具体的行列信息

devtool模式对比

  • eval
    • 将咱们的模块代码放到eval函数当中去执行,而且经过source url去标注文件的路径,这种模式下没有生成source map,它只能定位哪一个文件出了错误
  • eval-sourece-map
    • 一样使用eval函数去执行模块代码,它除了帮咱们定位错误的文件,还能帮咱们定位到行和列的信息,对比eval模式它生成了source map
  • cheap-eval-sourece-map
    • 阉割版的eval-sourece-map,生成的source map只有行的信息,没有列的信息,但速度会更快
    • es6转换够后的结果
  • cheap-module-eval-sourece-map
    • 和cheap-eval-sourece-map相似,不一样的是它定位的源代码就是咱们实际编写的源代码,没有通过转换的
  • cheap-source-map
    • 没有eval意味着没有用eval的方式去执行模块代码
    • 没有module意味着是通过loader处理事后的代码
  • inline-source-map
    • 和普通的source map模式同样
    • 普通模式是以物理文件地址方式存在
    • inline-source-map使用的是dataurl方式去嵌入到咱们的代码当中,体积会变大不少
  • hidden-source-map
    • 这种模式在构建过程中生成了map文件,可是代码当中并无经过注释的方式去引入这个文件
    • 开发第三方包的时候比较有用
  • nosources-source-map
    • 没有源代码,但提供了行列信息,为了在生产环境当中不会暴露源代码的状况
  • eval:是否使用eval执行模块代码
  • cheap:Source Map是否包含行信息
  • module:是否可以获得Loader处理以前的源代码

选择Source Map模式

  • 开发模式
    • cheap-module-eval-sourece-map
      • 代码每行不会超过80个字符
      • 通过Loader转换事后的差别较大,须要调试源代码
      • 首次打包速度慢无所谓,重写打包相对较快
  • 生产模式
    • none
      • Source Map会暴露源代码
      • 调试是开发阶段的事情,生成环境不建议使用source map
  • 理解不一样模式的差别,适配不一样的环境

自动刷新的问题

  • 问题:页面总体刷新,页面以前的操做状态会丢失
  • 需求:页面不刷新的前提下,模块也能够及时刷新

HMR体验

  • Hot Module Replacement(模块热替换/热更新)
  • 热拔插
    • 在一个正在运行的机器上随时插拔设备,而咱们机器的运行状态不会受插拔设备的影响,而插上的设备能够当即开始工做
    • 电脑上的USB端口能够热拔插
  • 模块热替换
    • 能够在应用程序运行的过程当中实时替换某个模块,应用运行状态不受影响
    • 自动刷新致使页面状态丢失,热替换只将修改的模块实时替换至应用中
  • HMR是Webpack中最强大的功能之一,极大程度提升了开发者的工做效率

开启HMR

  • HMR集成在webpack-dev-server中

    • webpack-dev-server --hot开启特性
    • 也能够在配置文件当中配置开启特性
    //热更新插件
    new webpack.HotModuleReplacementPlugin()
    复制代码

HMR疑问

  • Webpack中的HMR并不能够开箱即用
  • Webpack中的HMR须要手动处理模块热替换逻辑
  • 为何样式文件的热更新开箱即用?
    • 样式文件是通过loader处理的,在style-loader处理样式文件的过程当中就已经自动了热更新,因此不须要咱们额外作手动的操做
  • 凭什么样式能够自动处理,脚本文件要手动处理?
    • 样式文件处理事后只须要把css及时替换到页面当中,能够覆盖到以前的文件从而实现热更新
    • 编写的脚本文件是没有任何的规律的,webpack在面对这些毫无规律的JS模块不知道如何去处理这些更新事后的模块,没有办法帮咱们实现一个通用状况的模块替换方案
    • 个人项目没有手动处理,JS照样能够热替换?
      • 使用了vue-cli/create react等脚手架工具,框架下的开发,每种文件都是有规律的,框架提供的就是一些规则
      • 经过脚手架建立的项目内部都集成了HMR方案
  • 总结:咱们须要手动处理JS模块热更新后的热替换

使用HMR API

  • module.hot.accept('./editor', () => {
        console.log('editor模块更新了,须要这里手动处理热更新')
    })
    复制代码

处理JS模块热替换

//存储最后一次更新的值
let lastEditor = editor
module.hot.accept('./editor', () => {
    console.log('editor模块更新了,须要这里手动处理热更新', lastEditor.value)
    //拿到编辑的内容
    const value = lastEditor.value
    //移除原来的元素
    document.body.removeChild(editor)
    //建立一个新的元素
    const newEditor = createEditor()
    //将原来的只添加到新元素的值当中 避免原来的值丢失
    newEditor.value = value
    //将新元素追加到页面
    document.body.appendChild(newEditor)
    //记录最新的元素 不然下次找不到这个元素了
    lastEditor = newEditor
})
复制代码
  • webpack根本没有办法去提供一个通用的替换方案

处理图片模块热替换

//图片的热处理替换
module.hot.accept('./01.png', () => {
    img.src = background
    console.log(background)
})
复制代码

HMR注意事项

  • 处理HMR的代码报错会致使自动刷新,控制台错误信息就会被清除,不易察觉
  • 没启用HMR的状况下,HMR API会报错
  • 代码中多了一些与业务无关的代码

生产环境优化

  • 生成环境和开发环境有很大的差别
  • 生成环境注重运行效率
  • 开发环境注重开发效率
  • 模式(mode)
  • 为不一样的工做环境建立不一样的配置

不一样环境下的配置

  • 配置文件根据环境不一样导出不一样配置
  • 一个环境对应一个配置文件
/** * 不一样的环境返回不一样的配置 * @param {*} env CLI传递的环境名参数 * @param {*} argv 运行CLI过程当中所传递的全部参数 */
module.exports = (env, argv) => {
    //开发环境的配置
    const config = {
        //工做模式
        mode: 'none',
        //去指定webpack打包入口文件的路径
        entry: './src/main.js',
        //设置输出文件的配置
        output: {
            //输出文件的名称
            filename: 'bundle.js',
            //输出文件的路径(绝对路径)
            path: path.join(__dirname, 'dist'),
            //打包事后的文件具体存放位置
            // publicPath: 'dist/'
        },
        // webpack dev server的配置选项
        devServer: {
            //开启热更新 报错会从新刷新浏览器,不易调试
            hot: true,
            //不管代码是否被处理了热替换,浏览器都不会自动刷新
            hotOnly: true,
            //静态资源文件路径
            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
                    //代理路径重写
                    publicPath: {
                        '^/api': ''
                    },
                    //不能使用 localhost:8080 做为请求 github 的主机名
                    changeOrigin: true //以实际代理请求的主机名去请求
                }
            }
        },
        //配置开发过程当中的辅助工具
        devtool: 'eval',
        // 配置对象
        module: {
            //其余资源模块加载规则
            rules: [{
                    //匹配打包过程当中遇到的文件路径
                    test: /.css$/,
                    //匹配文件打包过程当中用的loader 配置了多个loader执行顺序是从后往前执行
                    use: [
                        'style-loader', //把css-loader转换后的结果经过style标签的形式添加到页面上
                        'css-loader' //处理css文件的加载器
                    ]
                },
                {
                    test: /.png$/,
                    use: {
                        loader: 'url-loader', //Data URLs加载器
                        //配置选项
                        options: {
                            limit: 10 * 1024 // 只将10kb如下的文件用url-loader处理
                        }
                    }
                }, {
                    test: /.html$/,
                    use: {
                        loader: 'html-loader', //html解析器
                        options: {
                            // html加载的时候对页面上的一些属性作一些额外处理
                            attrs: [
                                'img:src', //默认
                                'a:href'
                            ]
                        }
                    }
                }, {
                    test: /.md$/,
                    use: [
                        'html-loader',
                        './markdown-loader'
                    ]
                },
                // {
                // test: /.js$/,
                // use: {
                // loader: 'babel-loader', //处理es6代码当中的新特性
                // options: {
                // presets: ['@babel/preset-env'] //babel只是一个转换js代码的平台,在平台转换过程当中须要额外的插件
                // }
                // }
                // }
            ]
        },
        //配置插件
        plugins: [
            //清理输出目录
            //new CleanWebpackPlugin(),
            //用于拷贝文件到输出目录 开发阶段最好不要使用这个插件 影响效率
            // new CopyWebpackPlugin({
            // patterns: [
            // // 'public/**',
            // 'public'
            // ]
            // }),
            //自动生成index.html
            // new HtmlWebpackPlugin({
            // title: 'Webpack Plugin Sample',
            // meta: {
            // viewport: 'width=device-width'
            // },
            // filename: 'index.html'
            // }),
            //用于生成about.html
            new HtmlWebpackPlugin({
                filename: 'index.html'
            }),
            //自定义插件 删除生成的js文件当中的注释
            new MyPlugin(),
            //热更新插件
            new webpack.HotModuleReplacementPlugin()
        ]
    }

    if (env === 'production') {
        console.log('生成环境')
        config.mode = 'production'
        config.devtool = false
        config.plugins = [...config.plugins, new CleanWebpackPlugin()]
    } else {
        console.log('开发环境')
    }

    return config
}
复制代码
// 开发环境
yarn webpack

// 生产环境
yarn webpack --env production
复制代码

不一样环境的配置文件

  • 经过判断环境名参数去返回不一样的配置对象这种方式只适用于中小型项目,一旦项目变得复杂,配置文件也变得复杂起来
  • 对于大型项目建议使用不一样环境对应不一样配置文件来实现

DefinePlugin

  • 为代码注入全局成员
//为代码注入全局成员
new webpack.DefinePlugin({
    //符合JS语法的代码
    API_BASE_URL: '"https://api.example.com"'
})
复制代码

体验Tree Shaking

  • 检测代码中未引用的代码,而后移除掉它们
  • 在生产模式下自动开启

使用Tree Shaking

  • Tree Shaking不是指某个配置选项,它是一组功能搭配使用后的效果

  • production模式下自动开启

  • 其余模式开启

    //集中配置webpack内部的一些优化功能
    optimization: {
        //在输出结果中只导出被外部使用了的成员
        usedExports: true,
        //开启代码压缩功能
        minimize: true
    }
    复制代码

合并模块

  • concatenateModules
    • 尽量将全部模块合并到一块儿输出到一个函数中,即提高了运行效率,又减小了代码的体积

Tree Shaking与Babel

  • Tree Shaking的实现前提是ES Module
    • 由Webpack打包的代码必须使用ESM
    • 为了转换代码中的ECMScript新特性,不少时候使用babel-loader去处理JS,babel转换咱们的代码时有可能处理掉咱们使用的ES Modules,把它们转换成了CommonJS,取决于咱们有没有使用转换ESM的插件

sideEffects

  • sideEffects容许咱们经过配置的方式来标识咱们的代码是否有反作用,从而为Tree Shaking提供更大的压缩空间
  • 反作用:模块执行时除了导出成员以外所做的事情
  • sideEffects通常用于npm包标记是否有反作用

sideEffects注意

  • 确保你的代码真的没有反作用,不然webpack打包时会误删掉那些有反作用的代码

代码分割

webpack将咱们全部的代码都打包到一块儿,若是咱们的应用程序很是复杂,模块很是多的状况下,那么咱们的打包结果就会特别的大,咱们的应用程序开始工做时并非每一个模块在启动时都是必要的,比较合理的方案是分包,按需加载,这样就能大大提升咱们应用程序的相应速度和运行效率

多入口打包

  • 适用于传统的多页应用程序,一个页面对应一个打包入口,公共部分单独提取

提取公共模块

  • 多入口打包存在一个小问题,不一样的打包入口当中必定会存在一些公用的部分

    //把全部的公共模块提取到单独的bundle当中
    splitChunks: {
        chunks: 'all'
    }
    复制代码

动态导入

  • 须要用到某个模块时,在加载这个模块
  • 动态导入的模块会被自动分包

魔法注释

  • 若是须要给bundle命名的化可使用webpack提供的魔法注释去实现
  • 特有格式:/* webpackChunkName: '名称' */这样就能够给分包或者bundle起名字了
  • 若是名称相同,他们会被打包在一块儿

MiniCssExtractPlugin

  • 能够将css代码从打包结果当中提取出来
  • 经过这个插件能够实现css模块的按需加载

OptimizeCssAssetsWebpack

  • 能够压缩咱们的样式文件

输出文件名 Hash

  • 部署前端资源文件时,会开启静态资源缓存,对于用户的浏览器而言,它就能够缓存咱们的静态资源文件,提升咱们总体应用程序的相应速度

  • 生产模式下,文件名使用Hash,一旦资源文件发生改变,文件名称也会发生变化

  • hash:整个项目当中有任何一个地方发生改动,这一次打包过程中的hash值都会发生变化

  • chunkhash:在打包过程中只要是同一路的打包,chunkhash都是相同的

  • contenthash:根据输出文件的内容生成的hash值

  • 能够经过占位符的方式指定生成hash的长度,例如:[contenthash:8]

规范化标准

规范化介绍

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

常见的规范化实现方式

  • ESLint工具使用
  • 定制ESLint效验规则
  • ESLint对TypeScript的支持
  • ESLint结合自动化工具或者Webpack
  • 基于ESLint的衍生工具
  • Stylelint工具的使用

ESLint介绍

  • 最为主流的JavaScript Lint工具 监测JS代码质量
  • ESLint很容易统一开发者的编码风格
  • ESLint能够帮助开发者提高编码能力

安装

  • 初始化项目
  • 安装ESLint模块为开发依赖
  • 经过CLI命令验证安装结果

快速上手

  • ESLint检查步骤
    • 编写“问题”代码
    • 使用eslint执行检测
    • 完成eslint使用配置
  • 具体使用
    • yarn add eslint --dev
    • yarn eslint init
    • yarn eslint [文件名称]
    • yarn eslint [文件名称] --fix
      • fix参数能够自动解决绝大多数代码风格的问题

配置文件解析

  • env关键字可使用的环境,能够同时开启多个环境

    • browser - 浏览器环境中的全局变量。
    • node - Node.js 全局变量和 Node.js 做用域。
    • commonjs - CommonJS 全局变量和 CommonJS 做用域 (用于 Browserify/WebPack 打包的只在浏览器中运行的代码)。
    • shared-node-browser - Node.js 和 Browser 通用全局变量。
    • es6 - 启用除了 modules 之外的全部 ECMAScript 6 特性(该选项会自动设置 ecmaVersion 解析器选项为 6)。
    • worker - Web Workers 全局变量。
    • amd - 将 require()define() 定义为像 amd 同样的全局变量。
    • mocha - 添加全部的 Mocha 测试全局变量。
    • jasmine - 添加全部的 Jasmine 版本 1.3 和 2.0 的测试全局变量。
    • jest - Jest 全局变量。
    • phantomjs - PhantomJS 全局变量。
    • protractor - Protractor 全局变量。
    • qunit - QUnit 全局变量。
    • jquery - jQuery 全局变量。
    • prototypejs - Prototype.js 全局变量。
    • shelljs - ShellJS 全局变量。
    • meteor - Meteor 全局变量。
    • mongo - MongoDB 全局变量。
    • applescript - AppleScript 全局变量。
    • nashorn - Java 8 Nashorn 全局变量。
    • serviceworker - Service Worker 全局变量。
    • atomtest - Atom 测试全局变量。
    • embertest - Ember 测试全局变量。
    • webextensions - WebExtensions 全局变量。
    • greasemonkey - GreaseMonkey 全局变量。
  • extends

    • 共享配置,一个配置文件能够被基础配置中的已启用的规则继承
  • parserOptions

    • ESLint 容许你指定你想要支持的 JavaScript 语言选项。默认状况下,ESLint 支持 ECMAScript 5 语法。你能够覆盖该设置,以启用对 ECMAScript 其它版本和 JSX 的支持。

    • ecmaVersion - 默认设置为 3,5(默认), 你可使用 六、七、八、9 或 10 来指定你想要使用的 ECMAScript 版本。你也能够用使用年份命名的版本号指定为 2015(同 6),2016(同 7),或 2017(同 8)或 2018(同 9)或 2019 (same as 10)

    • sourceType - 设置为 "script" (默认) 或 "module"(若是你的代码是 ECMAScript 模块)。

    • ecmaFeatures
      复制代码

      - 这是个对象,表示你想使用的额外的语言特性:

      • globalReturn - 容许在全局做用域下使用 return 语句
      • impliedStrict - 启用全局 strict mode (若是 ecmaVersion 是 5 或更高)
      • jsx - 启用 JSX
      • experimentalObjectRestSpread - 启用实验性的 object rest/spread properties 支持。(**重要:**这是一个实验性的功能,在将来可能会有明显改变。 建议你写的规则 不要 依赖该功能,除非当它发生改变时你愿意承担维护成本。)
  • rules

    • 配置每一个校验规则的开启或者关闭
  • globals

    • 额外的声明代码中可使用的全局成员

配置注释

结合自动化工具

  • 集成以后,ESLint必定会工做
  • 与项目统一,管理更加方便

Prettier的使用

  • 近两年来使用较多的通用的代码格式化工具
  • yarn prettier . --write

Git Hooks介绍

  • 代码提交至仓库以前未执行lint工做
  • 经过Git Hooks在代码提交前强制lint
  • Git Hook也称为git钩子,每一个钩子都对应一个任务
  • 经过shell脚本能够执行钩子任务触发时要具体执行的操做

ESLint结合Git Hooks

  • Husky能够实现GitHooks的使用需求,提交以前强制验证咱们的代码
  • lint-staged 验证以后能够作一些其余操做,例如提交代码
相关文章
相关标签/搜索