模块化开发与规范化标准

200 阅读17分钟

一、模块化开发

  1.   模块化概述

    1. 模块化演变过程
    2. 模块化规范
    3. 常用的模块化打包工具
    4. 基于模块化工具构建现代web应用
    5. 打包工具的优势技巧
    
  2. 模块化演变过程

    1. stage1---基于文件划分的方式去实现的,完全依靠约定
      具体:
       具体做法就是将每个功能及其相关状态数据各自单独放到不同的文件中,
       约定每个文件就是一个独立的模块,使用某个模块就是将这个模块引入到页面中,
       然后直接调用模块中的成员(变量 / 函数)
     缺点:
      1. 污染全局作用域
       所有模块都直接在全局工作,没有私有空间,所有成员都可以在模块外部被访问或者修改
      2. 命名冲突问题
       而且模块一段多了过后,容易产生命名冲突,
      3. 无法管理模块依赖关系
       另外无法管理模块与模块之间的依赖关系
    2. stage2--- 命名空间方式:每个模块只暴露一个全局对象,所有模块成员都挂载到这个对象中
       具体:
        具体做法就是在第一阶段的基础上,通过将每个模块「包裹」为一个全局对象的形式实现,
        有点类似于为模块内的成员添加了「命名空间」的感觉。   缺点:
         通过「命名空间」减小了命名冲突的可能,
         但是同样没有私有空间,所有模块成员也可以在模块外部被访问或者修改,
         而且也无法管理模块之间的依赖关系。3. stage3--- 使用立即执行函数表达式为模块提供私有空间
       具体
        具体做法就是将每个模块成员都放在一个函数提供的私有作用域中,
        对于需要暴露给外部的成员,通过挂在到全局对象上的方式实现
       缺点:
        有了私有成员的概念,私有成员只能在模块成员内通过闭包的形式访问。
    
    4. stage4---利用 IIFE 参数作为依赖声明使用
       具体:
         具体做法就是在第三阶段的基础上,利用立即执行函数的参数传递模块依赖项。   缺点:
         这使得每一个模块之间的关系变得更加明显。
    
    5. stage5---
    
  3. 模块化规范的出现

    CommonJs规范 node中所提到的一个标准
    一个文件就是一个模块
    每个模块都有单独的作用域
    通过module.exports 导出成员
    通过require 函数载入模块
    
    require.js 实现了amd规范
    
    // 因为 jQuery 中定义的是一个名为 jquery 的 AMD 模块
    // 所以使用时必须通过 'jquery' 这个名称获取这个模块
    // 但是 jQuery.js 并不在同级目录下,所以需要指定路径
    // amd规范中约定每一个模块必须用define这个函数定义 1. 模块名称 2. 数组 3. 函数
    // 函数里的参数要和数组里的依赖项一一对应
    define('module1', ['jquery', './module2'], function ($, module2) {
      return { //导出
        start: function () {
          $('body').animate({ margin: '200px' })
          module2()
        }
      }
    })
    
    require.config({  
    paths: {    // 因为 jQuery 中定义的是一个名为 jquery 的 AMD 模块
        // 所以使用时必须通过 'jquery' 这个名称获取这个模块
        // 但是 jQuery.js 并不一定在同级目录下,所以需要指定路径
        jquery: './lib/jquery'  
    }
    })
    require(['./modules/module1'], function (module1) {
      module1.start()
    })
    // require 用来自动加载模块,define定义模块
    
  4. 模块化标准规范

    ES Modules 浏览器环境下的规范
    CommonJS node环境下的规范
    
  5. ES Modules 特性

      通过给 script 添加 type = module 的属性,就可以以 ES Module 的标准执行其中的 JS 代码了  <script type="module">    console.log('this is es module')  </script>
    
    在终端里运行 serve . 会显示一个地址,用浏览器打开,console里显示 this is es module
    
    特性:
    1.  ESM 自动采用严格模式,不能再全局范围内使用this  <script type="module">    console.log(this) // undefined  </script>2. 每个 ES Module 都是运行在单独的私有作用域中<script type="module">    var foo = 100    console.log(foo)  </script>  <script type="module">    console.log(foo) // 报错  </script>
    3. ESM 是通过 CORS 的方式请求外部 JS 模块的
    <script type="module" src="https://unpkg.com/jquery@3.4.1/dist/jquery.min.js"></script>
    
     4. ESM 的 script 标签会延迟执行脚本   <script defer src="demo.js"></script>// defer :推迟;  <p>需要显示的内容</p>
       4.1 如果添加一个type="module" 或者 defer 会等到网页运行完成再执行脚本
    
  6.  ES Modules 导出

    export 是对外导出接口
    import 是对内导入接口
    var name = 'foo module'function hello () {  console.log('hello')}class Person {}export { name, hello, Person }export {  name as names, // 将name 重命名为default  hello as fooHello// 将 hello 重命名为fooHello}export default name // 设置默认导出成员
    
  7.  ES Modules 导入导出的注意事项

    module.js
    var name = 'jack'var age = 18// var obj = { name, age }// export default { name, age } // 加上 default 正确导出一个对象// 这里的 `{ name, hello }` 不是一个对象字面量,// 它只是语法上的规则而已export { name, age }// export name // 错误的用法// export 'foo' // 同样错误的用法setTimeout(function () {  name = 'ben'}, 1000)
    
    app.js
    // CommonJS 中是先将模块整体导入为一个对象,然后从对象中结构出需要的成员// const { name, age } = require('./module.js')// ES Module 中 { } 是固定语法,就是直接提取模块导出成员import { name, age } from './module.js'console.log(name, age)// 导入成员并不是复制一个副本,// 而是直接导入模块成员的引用地址,// 也就是说 import 得到的变量与 export 导入的变量在内存中是同一块空间。// 一旦模块中成员修改了,这里也会同时修改,setTimeout(function () {  console.log(name, age)}, 1500)// 导入模块成员变量是只读的// name = 'tom' // 报错// 但是需要注意如果导入的是一个对象,对象的属性读写不受影响// name.xxx = 'xxx' // 正常
    
  8. ES Modules 导入用法

    // 表示加载这个模块并不提取他
    // import {} from './module.js'
    // import './module.js'// 直接可以引入 链接,CDN也可以引入
    // import { name } from 'http://localhost:3000/04-import/module.js'// console.log(name)// 把模块中所有导出的成员提取出来,用as 的方式把成员都放到一个对象当中,
    每一个成员都会成为对象的属性出现// import * as mod from './module.js'// console.log(mod)// 动态导入模块,异步的过程
    // import('./module.js').then(function (module) {//   console.log(module)// })// 模块中既有导出的成员,又有默认导出成员// abc 就是momodule.js里需要默认导出的
    title 成员// import abc, { name, age } from './momodule.js'// console.log(name, age, abc)
    
  9. ES Modules 浏览器环境 Polyfill

    ES Module Loader // 将里面的文件引入到网页,就可以让网页运行es Module
    
    <body>  <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>  <script type="module">    import { foo } from './module.js'    console.log(foo)  </script></body>//  第一个引入是 IE 不支持es语法,让其兼容
    //  第二个引入是 及时运行再浏览器上的版本
    //  第三个引入是 es-module-loader 让代码转换 网页可以使用
    nomodule 布尔值的属性,让其只会再不支持esmodule的浏览器中执行
    
  10.  ES Modules in Node.js - 支持情况

    node.js 8.5版本过后就开始支持es module

    inde.mjs // 第一,将文件的扩展名由 .js 改为 .mjs;// 第二,启动时需要额外添加 --experimental-modules 参数;// 运行 node --experimental-modules index.mjsimport { foo, bar } from './module.mjs'console.log(foo, bar)// 此时我们也可以通过 esm 加载内置模块了import fs from 'fs'fs.writeFileSync('./foo.txt', 'es module working') // 创建foo.txt,并填写内容// 也可以直接提取模块内的成员,内置模块兼容了 ESM 的提取成员方式import { writeFileSync } from 'fs'writeFileSync('./bar.txt', 'es module working')// 对于第三方的 NPM 模块也可以通过 esm 加载: yarn add ladashimport _ from 'lodash'_.camelCase('ES Module')// 不支持,因为第三方模块都是导出默认成员// import { camelCase } from 'lodash'// console.log(camelCase('ES Module'))

    module.mjs

    export const foo = 'hello'export const bar = 'world'

  11. ES Modules in Node.js - 与 CommonJS 交互

    es-module.mjs // ES Module 中可以导入 CommonJS 模块// import mod from './commonjs.js'// console.log(mod)// 不能直接提取成员,注意 import 不是解构导出对象// import { foo } from './commonjs.js'// console.log(foo)// export const foo = 'es module export value'

    CommonJS.js // CommonJS 模块始终只会导出一个默认成员// module.exports = {// foo: 'commonjs exports value'// }// exports.foo = 'commonjs exports value'// 不能在 CommonJS 模块中通过 require 载入 ES Module// const mod = require('./es-module.mjs')// console.log(mod)

    总结: 1. ES Modules中可以导入CommonJs模块 2. CommonJs中不可以导入ES Modules模块 3. CommonJs 始终只会导出一个默认成员 4. 注意import 不是结构导出对象

  12.  ES Modules in Node.js - 与 CommonJS 的差异

    // ESM 中没有模块全局成员了// // 加载模块函数// console.log(require)// // 模块对象// console.log(module)// // 导出对象别名// console.log(exports)// // 当前文件的绝对路径// console.log(__filename)// // 当前文件所在目录// console.log(__dirname)// -------------// require, module, exports 自然是通过 import 和 export 代替// __filename 和 __dirname 通过 import 对象的 meta 属性获取// const currentUrl = import.meta.url// console.log(currentUrl)// 通过 url 模块的 fileURLToPath 方法转换为路径import { fileURLToPath } from 'url'import { dirname } from 'path'const __filename = fileURLToPath(import.meta.url)const __dirname = dirname(__filename)console.log(__filename)console.log(__dirname)

  13.  ES Modules in Node.js - 新版本进一步支持

    common.cjs // 如果需要在 type=module 的情况下继续使用 CommonJS,// 需要将文件扩展名修改为 .cjsconst path = require('path')console.log(path.join(__dirname, 'foo'))

    index.js // Node v12 之后的版本,可以通过 package.json 中添加 type 字段为 module,// 将默认模块系统修改为 ES Module// 此时就不需要修改文件扩展名为 .mjs 了import { foo, bar } from './module.js'console.log(foo, bar)

    package.json :{ "type": "module"}

  14.  ES Modules in Node.js - Babel 兼容方案

    Babel 实现在低版本上使用es module

    1. 安装 yarn add @babel/node @babel/core @babel/preset-env --dev
    2. yarn babel-node
    3. yarn babel-node index.js --persets=@babel/perset-env
    

    // 如果觉得运行代码太长可以 建一个babelrc 的文件将配置放进去 1. yarn remove @babel/preset-env 2. yarn add @babel/plugin-transform-modules-commonjs --dev.babelrc

    { "plugins": [ "@babel/plugin-transform-modules-commonjs" ]}

二、webpack打包

  1. 模块打包工具的由来

    所有得前端资源都需要模块化
    好处:
     1. 可以编译代码,让浏览器兼容
     2. 可以模块化打包
     3. 支持不同种类的前端资源类型
    
  2. 模块打包工具概要

     webpack(打包工具)
     打包工具解决的是前端整体的模块化,并不单指Javascript模块化
       
    
  3. Webpack 快速上手

    主流的打包器
    简单的打包方法:
        package.json
    {  "name": "01-getting-started",  "version": "0.1.0",  "main": "index.js",  "author": "zce <w@zce.me> (https://zce.me)",  "license": "MIT",  "scripts": {    "build": "webpack" // 运行yarn build 就可以打包了  },  "devDependencies": {    "webpack": "^4.40.2", // 安装 yarn add webpack    "webpack-cli": "^3.3.9" //安装  yarn add webpack-cli  }}
    
  4. Webpack 配置文件

    在根目录文件下 新建一个webpack.config.js 文件,它是运行在node环境下的js文件
    webpack.config.js
    
    const path = require('path')module.exports = {  entry: './src/main.js', // 指定webpack打包的入口文件  output: {  // 设置输出文件    filename: 'bundle.js',  // 输出文件名称    path: path.join(__dirname, 'output') // 设置输出文件的所在目录,必须是绝对路径  }}
    
  5. Webpack 工作模式

    const path = require('path')module.exports = {
    //yarn webpack --mode development  // 这个属性有三种取值,分别是 production、development 和 none。   // 1. 生产模式下,Webpack 会自动优化打包结果;  // 2. 开发模式下,Webpack 会自动优化打包速度,添加一些调试过程中的辅助;  // 3. None 模式下,Webpack 就是运行最原始的打包,不做任何额外处理;  mode: 'development', //设置模式: 开发模式  entry: './src/main.js',  output: {    filename: 'bundle.js',    path: path.join(__dirname, 'dist')  }}
    
  6. Webpack 打包结果运行原理

    ctrl + K  && ctrl + 0 可以快速折叠代码到最上层
    
  7. Webpack 资源模块加载

    // npm install --save-dev style-loader css-loader
    // 加载css 文件
    const path = require('path')module.exports = {  mode: 'none',  entry: './src/main.css',  output: {    filename: 'bundle.js',    path: path.join(__dirname, 'dist')  },  module: {    rules: [ // 其他资源模块加载规则的配置      {        test: /.css$/,        use: [          'style-loader',          'css-loader'        ]      }    ]  }}
    
    // loader 是webpack的核心特性,借助于Loader就可以加载任何类型的资源
    
  8. Webpack 导入资源模块

    webpack.config.js // 配置文件
    // 加载css样式
    const path = require('path')module.exports = {  mode: 'none',  entry: './src/main.js',  output: {    filename: 'bundle.js',    path: path.join(__dirname, 'dist')  },  module: {    rules: [ // 加载css样式      {        test: /.css$/,        use: [          'style-loader',          'css-loader'        ]      }    ]  }}main.js // 入口文件
    
    import createHeading from './heading.js'import './main.css' // 引用cssconst heading = createHeading()document.body.append(heading)JavaScript 驱动整个前端应用
      逻辑合理 js确实需要这些资源文件
      确保上限资源不缺失,都是必要的
    
  9. Webpack 文件资源加载器

    npm install --save-dev file-loader
    // 加载图片package.config.jsconst path = require('path')module.exports = {  mode: 'none',  entry: './src/main.js',  output: {    filename: 'bundle.js',    path: path.join(__dirname, 'dist'),    publicPath: 'dist/' // 网站查看的根目录下  },  module: {    rules: [      {        test: /.css$/,        use: [          'style-loader',          'css-loader'        ]      },      { // 加载图片        test: /.png$/,         use: 'file-loader'      }    ]  }}
    
    main.js import createHeading from './heading.js'import './main.css'import icon from './icon.png'const heading = createHeading()document.body.append(heading)const img = new Image()img.src = icondocument.body.append(img)
    
  10.  Webpack URL 加载器

    npm install --save-dev url-loader // 加载url 让其上一步图片不会打包到dist文件夹里,他会转化未base64的形式 // 小文件使用 url-loader,减少请求次数 // 大文件单独提取存放,提高加载速度const path = require('path')module.exports = { mode: 'none', entry: './src/main.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'dist'), publicPath: 'dist/' }, module: { rules: [ { test: /.css/, use: [ 'style-loader', 'css-loader' ] }, { test: /.png/, use: { loader: 'url-loader', options: { // 添加图片配置选项 // 10kb以下的图片通过url-loader,以上则通过file-loader处理 limit: 10 * 1024 // 10 KB } } } ] }}

  11. Webpack 常用加载器分类

    // 1. 编译转换类:会把其他文件格式转化成js代码 style-loader css-loader // 2. 文件操作类:会把使用的文件,拷贝到输出目录 file-loader // 3. 代码检查类:统一代码风格

  12. Webpack 与 ES 2015

    const path = require('path')module.exports = { mode: 'none', entry: './src/main.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'dist'), publicPath: 'dist/' }, module: { rules: [ { test: /.js/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } }, { test: /.css/, use: [ 'style-loader', 'css-loader' ] }, { test: /.png$/, use: { loader: 'url-loader', options: { limit: 10 * 1024 // 10 KB } } } ] }}// webpack 只是打包工具 // 加载器可以用户来编译转换代码

  13.  Webpack 加载资源的方式

    webpack: 1. 遵循ESM标准import声明 2. 遵循CommonJs标准的require函数 3. 遵循AMD标准的define函数和require函数 4. 样式代码中的@import指令和url函数 5. THTML代码中图片标签的src属性 webpack.config.js

    // npm i html-loader --dev const path = require('path')module.exports = { mode: 'none', entry: './src/main.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'dist'), publicPath: 'dist/' }, module: { rules: [ { test: /.js/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } }, { test: /.css/, use: [ 'style-loader', 'css-loader' ] }, { test: /.png/, use: { loader: 'url-loader', options: { limit: 10 * 1024 // 10 KB } } }, { test: /.html/, use: { loader: 'html-loader', options: { attrs: ['img:src', 'a:href'] } } } ] }}mian.js

    import './main.css' import footerHtml from './footer.html' document.write(footerHtml)

  14. Webpack 核心工作原理

    const path = require('path')// Loader机制是webpack的核心

  15. Webpack 开发一个 Loader

    // npm i marked --dev const path = require('path')// Loader 工作管道的特性module.exports = { mode: 'none', entry: './src/main.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'dist'), publicPath: 'dist/' }, module: { rules: [ { test: /.md$/, use: [ 'html-loader', './markdown-loader' ] } ] }}

    markdown.js

    const marked = require('marked')module.exports = source => { // return 出必须是javascript代码 // 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}// Loader负责资源文件从输入到输出的转换 // 对于同一个资源可以依次使用多个Loader

  16. Webpack 插件机制介绍

    // 增加webpack自动化能力 // plugin解决其他自动化工作

  17.  Webpack 自动清除输出目录插件和 自动生成HTML插件

    // npm i clean-webpack-plugin --dev // 自动清除输出目录 // npm i html-webpack-plugin // 自动生成html

    const path = require('path') const webpack = require('webpack') const { CleanWebpackPlugin } = require('clean-webpack-plugin') // 载入 const HtmlWebpackPlugin = require('html-webpack-plugin')// 载入 module.exports = {
    mode: 'none', entry: './src/main.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'dist'), // publicPath: 'dist/' // 自动在dist生成index.html,就不需要这个配置了,也不需要 根目录下的index.html文件了
    }, module: { rules: [ { test: /.css/, use: [ 'style-loader', 'css-loader' ] }, { test: /.png/, use: { loader: 'url-loader', options: { limit: 10 * 1024 // 10 KB } } } ] }, plugins: [ // 配置插件 new webpack.ProgressPlugin(), new CleanWebpackPlugin(), // 清除dist里上次打包的多余文件 new HtmlWebpackPlugin() // 自动生成html文件输出到dist文件里 <-------------------------------------> // 用于生成 index.html //new HtmlWebpackPlugin({ //title: 'Webpack Plugin Sample',// 配置html文件的title //meta: { // viewport: 'width=device-width' // 配置html里的mate标签 // }, //template: './src/index.html' //}),

         // 用于生成about.html文件; ps:生成多个文件     // new HtmlWebpackPlugin({     //   filename: 'about.html'      //})
    

    <-------------------------------------> ] }

    对应上面<------> src/index.html<----->

    Webpack

    <%= htmlWebpackPlugin.options.title %>

    // 模板
  18. Webpack 插件使用总结

    // npm i copy-webpack-plugin // 用于把开发中pulic静态目录中的文件,也拷贝到输出文件中 const path = require('path')const { CleanWebpackPlugin } = require('clean-webpack-plugin')const HtmlWebpackPlugin = require('html-webpack-plugin')const CopyWebpackPlugin = require('copy-webpack-plugin')module.exports = { mode: 'none', entry: './src/main.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'dist'), // publicPath: 'dist/' }, module: { rules: [ { test: /.css/, use: [ 'style-loader', 'css-loader' ] }, { test: /.png/, use: { loader: 'url-loader', options: { limit: 10 * 1024 // 10 KB } } } ] }, plugins: [ new CleanWebpackPlugin(), // 用于生成 index.html new HtmlWebpackPlugin({ title: 'Webpack Plugin Sample', meta: { viewport: 'width=device-width' }, template: './src/index.html' }), // 用于生成 about.html new HtmlWebpackPlugin({ filename: 'about.html' }), new CopyWebpackPlugin([ // 拷贝静态目录 // 'public/**' 'public' ]) ]}

  19.  Webpack 开发一个插件

    相比于Loader,Plugin 拥有更宽的能力范围 Plugin 通过狗子机制实现 webpack 要求插件必须是一个函数或者是一个包含apply方法的对象

    webpack.config.js

    const path = require('path')const { CleanWebpackPlugin } = require('clean-webpack-plugin')const HtmlWebpackPlugin = require('html-webpack-plugin')const CopyWebpackPlugin = require('copy-webpack-plugin')class MyPlugin { 定义一个类型 里面定义一个方法 apply (compiler) { console.log('MyPlugin 启动') compiler.hooks.emit.tap('MyPlugin', compilation => { // compilation => 可以理解为此次打包的上下文 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 } } } }) }}module.exports = { mode: 'none', entry: './src/main.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'dist'), // publicPath: 'dist/' }, module: { rules: [ { test: /.css/, use: [ 'style-loader', 'css-loader' ] }, { test: /.png/, use: { loader: 'url-loader', options: { limit: 10 * 1024 // 10 KB } } } ] }, plugins: [ new CleanWebpackPlugin(), // 用于生成 index.html new HtmlWebpackPlugin({ title: 'Webpack Plugin Sample', meta: { viewport: 'width=device-width' }, template: './src/index.html' }), // 用于生成 about.html new HtmlWebpackPlugin({ filename: 'about.html' }), new CopyWebpackPlugin([ // 'public/**' 'public' ]), new MyPlugin() // 配置使用上面的类,去删除bundle.js 里多余的注释 ]}插件是通过在生命周期的钩子中挂载函数实现扩展

  20. Webpack 开发体验问题

    // 设想:理想的开发环境

    1. 以HTTP Server运行
    2. 自动运行,报错浏览器能快速定位
  21. Webpack 自动编译

    watch 的工作模式: 监听文件变化,自动重新打包

    yarn webpack -watch

  22.  Webpack 自动刷新浏览器

    运行 BrowserSync dist --files "**/*" 原理:webpack自动打包到dist当中,dist文件里的变化又被BrowserSync监听了,从而浏览器自动刷新

    缺点:

    1. 操作上麻烦,需要同时操作两个工具
    2. 效率上降低了
  23. Webpack Dev Server

    提供用于开发的THHP Server 集成【自动编译】和【自动刷新浏览器】等功能

    yarn add webpack-dev-server --dev // 安装dev-server 做为开发机制

    运行 yarn webpack-dev-server 自动监听代码变化
    

    yarn webpack-dev-server --open 会自动打开浏览器

  24. Webpack Dev Server 静态资源访问

    Dev Server 默认只会serve打包输出文件,只要是webpack打包输出的文件,都可以正常被访问 其他静态资源文件也需要serve

    const path = require('path')const webpack = require('webpack')const { CleanWebpackPlugin } = require('clean-webpack-plugin')const HtmlWebpackPlugin = require('html-webpack-plugin')const CopyWebpackPlugin = require('copy-webpack-plugin')module.exports = { mode: 'development', entry: './src/main.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'dist') }, module: { rules: [ { test: /.css/, use: [ 'style-loader', 'css-loader' ] }, { test: /.png/, use: { loader: 'url-loader', options: { limit: 10 * 1024 // 10 KB } } } ] }, devtool: 'inline-source-map', devServer: { //专门用来为webpack-dev-server来指定相关的属性 contentBase: './dist', // 指定额外的静态资源文件 hot: true, open: true }, plugins: [ new CleanWebpackPlugin(), // 用于生成 index.html new HtmlWebpackPlugin({ title: 'Ideal Webpack Develop Env', meta: { viewport: 'width=device-width' }, template: './src/index.html' }), //new CopyWebpackPlugin([ // 开发阶段最好不要使用这个插件 // 'public/**' // 'public' //]), new webpack.HotModuleReplacementPlugin() ]}

  25.  Webpack Dev Server 代理 API

    开发阶段接口跨域问题 Webpack Dev Server 支持配置代理 目标:将GitHub API代理到开发服务器

    const path = require('path')const { CleanWebpackPlugin } = require('clean-webpack-plugin')const HtmlWebpackPlugin = require('html-webpack-plugin')const CopyWebpackPlugin = require('copy-webpack-plugin')module.exports = { mode: 'none', entry: './src/main.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'dist') }, devServer: { contentBase: './public', proxy: { // 添加代理 '/api': { // http://localhost:8080/api/users -> api.github.com/api/users target: 'api.github.com', // http://localhost:8080/api/users -> api.github.com/users pathRewrite: { // 代理路径的重写 '^/api': '' }, // 不能使用 localhost:8080 作为请求 GitHub 的主机名 changeOrigin: true } } }, module: { rules: [ { test: /.css/, use: [ 'style-loader', 'css-loader' ] }, { test: /.png/, use: { loader: 'url-loader', options: { limit: 10 * 1024 // 10 KB } } } ] }, plugins: [ new CleanWebpackPlugin(), // 用于生成 index.html new HtmlWebpackPlugin({ title: 'Webpack Tutorials', meta: { viewport: 'width=device-width' }, template: './src/index.html' }), // // 开发阶段最好不要使用这个插件 // new CopyWebpackPlugin(['public']) ]}

  26. Source Map 介绍

    Source Map 映射转换过后的代码与源代码之间的关系,通过soure 逆向得到源代码 列子: 在jquery.js 最后一行 添加 //# sourceMappingURL=jquer-3.4.1.min.map 在浏览器中如果打开开发人员工具的话,加载js文件,最后一行有一行这样的注释,他就会 自动请求这个source这个服务,根据文件内容逆向解析源代码 Source Map 解决了源代码与运行代码不椅子所产生的问题

  27. Webpack 配置 Source Map

    const path = require('path')const { CleanWebpackPlugin } = require('clean-webpack-plugin')const HtmlWebpackPlugin = require('html-webpack-plugin')const CopyWebpackPlugin = require('copy-webpack-plugin')module.exports = { mode: 'none', entry: './src/main.js', output: { filename: 'bundle.js', path: path.join(__dirname, 'dist') }, devtool: 'source-map', // 用来配置开发过程中的辅助工具与 Source Map相关的辅助工具 module: { rules: [ { test: /.css/, use: [ 'style-loader', 'css-loader' ] }, { test: /.png/, use: { loader: 'url-loader', options: { limit: 10 * 1024 // 10 KB } } } ] }, plugins: [ new CleanWebpackPlugin(), // 用于生成 index.html new HtmlWebpackPlugin({ title: 'Webpack Tutorials', meta: { viewport: 'width=device-width' }, template: './src/index.html' }), new CopyWebpackPlugin([ // 'public/**' 'public' ]) ]}

  28.  Webpack eval 模式的 Source Map和Webpack devtool 模式对比

    const HtmlWebpackPlugin = require('html-webpack-plugin') eval - 是否使用eval执行模块代码 cheap-Source Map 是否包括行信息 module - 是否能够得到Loader处理之前的源代码 const allModes = [ 'eval',// 可以运行js代码: eval(console.log('123)) 'cheap-eval-source-map', 'cheap-module-eval-source-map', 'eval-source-map', 'cheap-source-map', 'cheap-module-source-map', 'inline-cheap-source-map', 'inline-cheap-module-source-map', 'source-map', 'inline-source-map', 'hidden-source-map', 'nosources-source-map' // 浏览器报错提供行列信息,但是在开发者里看不到源代码 ] module.exports = allModes.map(item => { return { devtool: item, mode: 'none', entry: './src/main.js', output: { filename: js/${item}.js }, module: { rules: [ { test: /.js/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } } ] }, plugins: [ new HtmlWebpackPlugin({ filename: `{item}.html` }) ] } }) // module.exports = [ // { // entry: './src/main.js', // output: { // filename: 'a.js' // } // }, // { // entry: './src/main.js', // output: { // filename: 'b.js' // } // } // ]

  29. Webpack 选择 Source Map 模式

    开发环境: cheap-module-eval-source-map 1. 每行代码不会超过80个字符 2. 经过Loader转换过后的差异较大 3. 首次打包速度慢无所谓,重新打包相对较快 生产环境:none 1. Source Map 会暴露源代码。使用nonde避免暴露

  30. Webpack 自动刷新的问题

    自动刷新,在网页上编辑的文字会丢失

  31. Webpack HMR 体验

    模块热更新 使用HMR使现编的文字刷新不会消失1.使用 yarn webpack-dev-server --hot开启 2. 在package.config.js里配置hot const webpack = require('webpack') const HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { mode: 'development', entry: './src/main.js', output: { filename: 'js/bundle.js' }, devtool: 'source-map', devServer: { hot: true // hotOnly: true // 只使用 HMR,不会 fallback 到 live reloading }, module: { rules: [ { test: /.css/, use: [ 'style-loader', 'css-loader' ] }, { test: /\.(png|jpe?g|gif)/, use: 'file-loader' } ] }, plugins: [ new HtmlWebpackPlugin({ title: 'Webpack Tutorial', template: './src/index.html' }), new webpack.HotModuleReplacementPlugin() ] }

  32. Webpack 开启 HMR

    HMR并不刻意开箱即用 Webpack 中的HMR需要手动处理模块热替换逻辑

  33.  Webpack HMR 的疑问

    总结:需要手动处理JS模块更新后的热替换

  34.  Webpack 使用 HMR API

    // 当某一个模块更新后替换到当前运行的页面中,且不会刷新 // 第一个是依赖模块的路径,第二个是依赖更新的处理函数 // module.hot.accept('./edtor', () => { // console.log(1) // })

  35.  Webpack 处理 JS 模块热替换

    main.js
    // 自动刷新的功能 // module.hot.accept('./editor.js', () => {// module.hot.accept('./editor.js', () => {// const value = hotEditor.innerHTML // 先存储页面上的输入值// document.body.removeChild(hotEditor) // 然后再进行移出// hotEditor = createEditor() // 重新生成编辑器// hotEditor.innerHTML = value // 再将保存的值 录入进去// document.body.appendChild(hotEditor) // 显示再页面// }) // 页面有输入的文字,修改代码时,让页面刷新不会把实时输入的文字删除 // 适用在小面积有文字输入的页面

  36. Webpack 处理图片模块热替换

    main.js

    // module.hot.accept('./better.png', () => {// img.src = background// console.log(1)// })

  37. Webpack HMR 注意事项

    如果代码中有错误,热替换会先把错误移出掉,再进行替换,这样会使错误无法即使发现

    使用hotOnly的方式 如果代码错误就不会使用自动刷新功能

    webpack.config.js

    devServer: { hotOnly: true }

    如果启用HMR的情况下,HMR API 报错怎么办?

     解决方式:再mian.js里调用时,先去判断是否有HMR
       例如:
    

    // if (module.hot) { // 使用if先去判断有没有HMR// module.hot.accept('./better.png', () => {// img.src = background// console.log(1)// })// }

  38. Webpack 生产环境优化

    为不同工作环境创建不同的配置

  39. Webpack 不同环境下的配置

    1. 配置文件根据环境不同导出不同配置
    2. 一个环境对应一个配置文件

    第一种方式:适用于中小型项目 const webpack = require('webpack')const { CleanWebpackPlugin } = require('clean-webpack-plugin')const HtmlWebpackPlugin = require('html-webpack-plugin')const CopyWebpackPlugin = require('copy-webpack-plugin') // 第一个通过cli传递的环境名参数,第二个是运行cli的所有参数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.common.js ,webpack.dev.js(开发环境),webpack.prod.js(生产环境) webpack.common.js const HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = { entry: './src/main.js', output: { filename: 'js/bundle.js' }, 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' }) ]}webpack.dev.js(开发环境)const webpack = require('webpack')const merge = require('webpack-merge')const common = require('./webpack.common')module.exports = merge(common, { mode: 'development', devtool: 'cheap-eval-module-source-map', devServer: { hot: true, contentBase: 'public' }, plugins: [ new webpack.HotModuleReplacementPlugin() ]}) webpack.prod.js(生产环境) // 测试生产环境配置是否生效:yarn webpack --config webpack.prod.jsconst merge = require('webpack-merge') // 合并const { CleanWebpackPlugin } = require('clean-webpack-plugin')const CopyWebpackPlugin = require('copy-webpack-plugin')const common = require('./webpack.common')module.exports = merge(common, { // 合并公用代码和修改的代码 mode: 'production', plugins: [ new CleanWebpackPlugin(), new CopyWebpackPlugin(['public']) ]})

超出字符限制,下面内容网址  juejin.cn/post/690983…