1、模块化演变过程

立刻执行函数

2、commonjs标准

一个文件就是一个模块
每个模块都有点独自的作用域
通过module.exports到处
通过require导入

commonjs是以同步模式加载模块

node没问题然而浏览器段有问题

所以就要应用amd标准,require.js

// 因为 jQuery 中定义的是一个名为 jquery 的 AMD 模块// 所以应用时必须通过 'jquery' 这个名称获取这个模块// 然而 jQuery.js 并不在同级目录下,所以须要指定门路define('module1', ['jquery', './module2'], function ($, module2) {  return {    start: function () {      $('body').animate({ margin: '200px' })      module2()    }  }})require(['./modules/module1'], function (module1) {  module1.start()})

应用起来较为简单,然而生态比拟好,模块划分过于粗疏的话js文件申请频繁

3、模块化标准规范

浏览器:ES Modules

node:CommonJS

4、ES Modules 根本个性

  <!-- 通过给 script 增加 type = module 的属性,就能够以 ES Module 的规范执行其中的 JS 代码了 -->  <script type="module">    console.log('this is es module')  </script>  <!-- 1. ESM 主动采纳严格模式,疏忽 'use strict' -->  <script type="module">    console.log(this)  </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>  <p>须要显示的内容</p>

5、ESmodule导出

 var obj = { name, age }export default { name, age }//导出一个对象,属性为name,ageexport { name, age }// export name // 谬误的用法// export 'foo' // 同样谬误的用法setTimeout(function () {  name = 'ben'}, 1000)//援用方也会在一秒钟之后更改

6、import

// import { name } from 'module.js'// import { name } from './module.js'// import { name } from '/04-import/module.js'// import { name } from 'http://localhost:3000/04-import/module.js'// var modulePath = './module.js'// import { name } from modulePath// console.log(name)// if (true) {//   import { name } from './module.js'// }动静导入,间接援用地址// import('./module.js').then(function (module) {//   console.log(module)// })//命名成员和默认成员都要导入进去// import { name, age, default as title } from './module.js'import abc, { name, age } from './module.js'console.log(name, age, abc)

7、

  <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>

加上nomodule 就能够实现不反对esmodule的浏览器动静加载这个脚本

8、

ES Modules能够导入Common JS模块
CommonJs不能导入ES Modules模块
CommonJS始终导出一个默认成员
留神import不是解构导出对象

9、打包的由来

ES Modules的弊病:

模块化须要解决兼容

模块化的网络申请太频繁,因为每一个文件都要从服务端

所有前端资源除了JS之外都是须要模块化的

打包工具的性能:

编译JS新个性

生产阶段须要打包为一个js文件

反对不同品种的资源类型

10、模块打包工具

webpack:

模块打包器

兼容性解决:loader

代码拆分

资源模块:容许应用js引入其余资源

11、webpack疾速上手

12、webpack运行原理

bundle这个文件是一个立刻执行函数

传入参数就是代码里的每一个模块

有一个对象来贮存加载过的模块,

会依照进入程序加载模块,

13、资源模块加载

不同的资源文件须要配置不同的loader加载器

在rules外面配置

rules:[{  test:/.css$/,  use:[    'style-loader',    ‘css-loader'  ]}]

14、webpack导入资源模块

个别还是以js文件为入口

那么其余资源的文件

例如在js文件引入css文件

将整个我的项目变成了js驱动的我的项目

15、webpack文件资源加载器

装置file-loader加载器

rules:[{  test:/.png$/,  use:[    ‘file-loader',    ‘css-loader'  ]}]output:{   puclicPath:’dist/‘ 根目录文件,这样图片能力找到那个地址}

16、URL加载器

use:{  Loader:url-loader,  options:{    Limit: 10 * 1024//低于这个大小才用url-loader  }}]

小文件用url-loader

17、 js转换

      {        test: /.js$/,        use: {          loader: 'babel-loader',          options: {            presets: ['@babel/preset-env']          }        }      },

18、模块加载形式

遵循ES

遵循Common

遵循AMD的define函数和require函数

Loader加载的时候也会触发

款式代码中的@import指令和url函数
@import url(reset.css);

HTML中的src属性和href属性

  {    test: /.html$/,    use: {      loader: 'html-loader',      options: {        attrs: ['img:src', 'a:href']      }    }  }

19、loader工作原理

markdown-loaderconst marked = require('marked')module.exports = source => {  // console.log(source)  // return 'console.log("hello ~")'这里是因为必须返回js语句  const html = marked(source)  // return html  // return `module.exports = "${html}"` 这样子会导致换行符等被疏忽  // return `export default ${JSON.stringify(html)}` 转成json就能够防止这个问题  // 返回 html 字符串交给下一个 loader 解决  return html}    rules: [      {        test: /.md$/,        use: [ //程序是从后往前          'html-loader',          './markdown-loader'        ]      }

20、插件机制

加强自动化能力,例如压缩代码

loader是用来加强资源加载能力的

21、罕用插件

主动清理输入目录的插件

  plugins: [    new webpack.ProgressPlugin(),    new CleanWebpackPlugin(),    new HtmlWebpackPlugin()  ]

依据模板动静生成,动静输入

  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'    })  ]

22、开发一个插件

插件比起loader有着更宽泛的能力

实质是钩子机制,webpack打包全流程中会提供钩子

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          }        }      }    })  }}

23、dev server

devServer: {contentBase: './public’, 额定资源门路    proxy: {      '/api': {//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': '' //正则表达式替换掉api字符        },        // 不能应用 localhost:8080 作为申请 GitHub 的主机名        changeOrigin: true      }    }}},

24、source map

调试和报错的根底,源代码的地图

//# sourceMappingURL=jquery-3.4.1.min.map

这个正文能够在浏览器当中映射出源代码

25、webpack配置

devtool: 'source-map',//这个设置能够实现source map

26、webpack eval模式的source map

devtool: 'eval',

通过eval函数执行,并且在结尾附上那段正文

构建速度是最快的

然而很难定位行列信息,只能定位到文件

27、不同devtool模式比照

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`            })        ]    }})
'eval','cheap-eval-source-map', //阉割版的source map,定位时定位在了源代码'cheap-module-eval-source-map','eval-source-map','cheap-source-map','cheap-module-source-map',//阉割版的source map,定位时定位在了'inline-cheap-source-map','inline-cheap-module-source-map','source-map','inline-source-map','hidden-source-map','nosources-source-map'

eval-是否应用eval执行模块代码
cheap-是否蕴含行信息
module-是否可能失去loader解决之前的源代码

inline-把sourcemap嵌入到代码当中
hidden - 没有source map的成果
nosources-能看到错误信息,然而浏览器下面看不到

如何抉择呢?

抉择适合的source map

开发模式中:cheap-module-eval-source-map

起因:

代码每行不超过80个字符
通过loader转换后变动较大
首次打包速度慢无所谓,从新打包较快

生产模式中:none

source map会裸露源代码

28、主动刷新

主动刷新会导致页面状态失落

页面不刷新

29、HMR(模块热更新)

在运行过程的即时替换,利用运行状态不受影响

  plugins: [    new webpack.HotModuleReplacementPlugin()  ]  devServer: {    hot: true    // hotOnly: true // 只应用 HMR,不会 fallback 到 live reloading  },// ============ 以下用于解决 HMR,与业务代码无关 ============// main.jsif (module.hot) {先判断是否存在这个模块  let lastEditor = editor  module.hot.accept('./editor', () => {    // console.log('editor 模块更新了,须要这里手动解决热替换逻辑')    // console.log(createEditor)    const value = lastEditor.innerHTML    document.body.removeChild(lastEditor)    const newEditor = createEditor()    newEditor.innerHTML = value    document.body.appendChild(newEditor)    lastEditor = newEditor  })  module.hot.accept('./better.png', () => {    img.src = background    console.log(background)  })}

HMR的非凡逻辑会在编译之后被清掉

30、生产环境优化

mode的用法

不同环境下的配置

①、配置文件依据环境不同导出不同配置

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()    ]  }  if (env === 'production') {    config.mode = 'production'    config.devtool = false    config.plugins = [      ...config.plugins,      new CleanWebpackPlugin(),      new CopyWebpackPlugin(['public'])    ]  }  return config}    

②、一个环境对应一个配置文件

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()  ]})

31、DefinePlugin

为代码注入

plugins: [new webpack.DefinePlugin({// 值要求的是一个代码片段API_BASE_URL: JSON.stringify('https://api.example.com')})]

32、treeShaking

optimization: {// 模块只导出被应用的成员,标记枯树枝usedExports: true,// 尽可能合并每一个模块到一个函数中,作用域晋升concatenateModules: true,// 压缩输入后果,把树枝要下来// minimize: true}

生产环境中会主动开启

33、Tree-shaking & Babel

必须应用ES Modules

babel-loader

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 转换      // 设置为commonjs会强制转换          ['@babel/preset-env', { modules: 'auto' }]       ]     }}}]},optimization: {// 模块只导出被应用的成员usedExports: true,// 尽可能合并每一个模块到一个函数中// concatenateModules: true,// 压缩输入后果// minimize: true}

34、sideEffects

optimization: {

sideEffects: true,

}

如果一个模块里被援用进来然而只用外面的一个模块,那么其它模块就不会被引入

import { Button } from './components'
//components当中除了Button之外的模块都不会被引入

要确保你的代码没有副作用

// 副作用模块
import './extend'

在extend当中为number原型增加了一个办法,这样就会导致这个办法是没有被引入的

解决办法

"sideEffects": [
"./src/extend.js",
"*.css"
]

在package.json文件中

35、代码宰割

并不是每个模块都是在启动时须要的

所以须要实现按需加载

然而也不能分的太细

同域并行申请

申请头占资源

两种解决办法

①多入口打包

entry: {index: './src/index.js',album: './src/album.js'},留神这里是对象,数组的话就是多个文件打包到一个文件output: {filename: '[name].bundle.js’//编译完结后会替换[name]},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’] //指定加载那个打包文件})]提取公共模块  optimization: {    splitChunks: {      // 主动提取所有公共模块到独自 bundle      chunks: 'all'    }  },

②动静导入

按需加载

const render = () => {  const hash = window.location.hash || '#posts'  const mainElement = document.querySelector('.main')  mainElement.innerHTML = ''  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())    })  }}render()window.addEventListener('hashchange', render)

36、minicss

  module: {    rules: [      {        test: /\.css$/,        use: [          // 'style-loader', // 将款式通过 style 标签注入          MiniCssExtractPlugin.loader,          'css-loader'        ]      }    ]  },

37、OptimizeCssAssetsWebpackPlugin

  optimization: {    //配置在这里能够保障只有压缩性能开启时才会执行上面插件    //所以须要本人手动保障一下js的压缩    minimizer: [      new TerserWebpackPlugin(),      new OptimizeCssAssetsWebpackPlugin() 压缩款式文件    ]  },

38、输入文件名hash

  output: {    filename: '[name]-[contenthash:8].bundle.js'  },

通过hash值管制缓存