一、模块化演变过程css
当即执行函数html
二、commonjs规范前端
一个文件就是一个模块
每一个模块都有点单独的做用域
经过module.exports处处
经过require导入node
commonjs是以同步模式加载模块jquery
node没问题可是浏览器段有问题webpack
因此就要使用amd规范,require.jsgit
// 由于 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文件请求频繁github
三、模块化标准规范web
浏览器:ES Modules正则表达式
node:CommonJS
四、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>
五、ESmodule导出
var obj = { name, age } export default { name, age } //导出一个对象,属性为name,age export { name, age } // export name // 错误的用法 // export 'foo' // 一样错误的用法 setTimeout(function () { name = 'ben' }, 1000) //引用方也会在一秒钟以后更改
六、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)
七、
<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的浏览器动态加载这个脚本
八、
ES Modules能够导入Common JS模块
CommonJs不能导入ES Modules模块
CommonJS始终导出一个默认成员
注意import不是解构导出对象
九、打包的由来
ES Modules的弊端:
模块化须要处理兼容
模块化的网络请求太频繁,由于每个文件都要从服务端
全部前端资源除了JS以外都是须要模块化的
打包工具的功能:
编译JS新特性
生产阶段须要打包为一个js文件
支持不一样种类的资源类型
十、模块打包工具
webpack:
模块打包器
兼容性处理:loader
代码拆分
资源模块:容许使用js引入其余资源
十一、webpack快速上手
十二、webpack运行原理
bundle这个文件是一个当即执行函数
传入参数就是代码里的每个模块
有一个对象来储存加载过的模块,
会按照进入顺序加载模块,
1三、资源模块加载
不一样的资源文件须要配置不一样的loader加载器
在rules里面配置
rules:[ { test:/.css$/, use:[ 'style-loader', ‘css-loader' ] }]
1四、webpack导入资源模块
通常仍是以js文件为入口
那么其余资源的文件
例如在js文件引入css文件
将整个项目变成了js驱动的项目
1五、webpack文件资源加载器
安装file-loader加载器
rules:[ { test:/.png$/, use:[ ‘file-loader', ‘css-loader' ] }] output:{ puclicPath:’dist/‘ 根目录文件,这样图片才能找到那个地址 }
1六、URL加载器
use:{ Loader:url-loader, options:{ Limit: 10 * 1024//低于这个大小才用url-loader } }]
小文件用url-loader
1七、 js转换
{ test: /.js$/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'] } } },
1八、模块加载方式
遵循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'] } } }
1九、loader工做原理
markdown-loader const 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是用来加强资源加载能力的
2一、经常使用插件
自动清理输出目录的插件
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' }) ]
2二、开发一个插件
插件比起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 } } } }) } }
2三、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 } } } },
2四、source map
调试和报错的基础,源代码的地图
//# sourceMappingURL=jquery-3.4.1.min.map
这个注释能够在浏览器当中映射出源代码
2五、webpack配置
devtool: 'source-map',//这个设置能够实现source map
2六、webpack eval模式的source map
devtool: 'eval',
经过eval函数执行,而且在结尾附上那段注释
构建速度是最快的
可是很难定位行列信息,只能定位到文件
2七、不一样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会暴露源代码
2八、自动刷新
自动刷新会致使页面状态丢失
页面不刷新
2九、HMR(模块热更新)
在运行过程的即时替换,应用运行状态不受影响
plugins: [ new webpack.HotModuleReplacementPlugin() ] devServer: { hot: true // hotOnly: true // 只使用 HMR,不会 fallback 到 live reloading }, // ============ 如下用于处理 HMR,与业务代码无关 ============ // main.js if (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() ] })
3一、DefinePlugin
为代码注入
plugins: [ new webpack.DefinePlugin({ // 值要求的是一个代码片断 API_BASE_URL: JSON.stringify('https://api.example.com') }) ]
3二、treeShaking
optimization: { // 模块只导出被使用的成员,标记枯树枝 usedExports: true, // 尽量合并每个模块到一个函数中,做用域提高 concatenateModules: true, // 压缩输出结果,把树枝要下来 // minimize: true }
生产环境中会自动开启
3三、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 }
3四、sideEffects
optimization: {
sideEffects: true,
}
若是一个模块里被引用进来可是只用里面的一个模块,那么其它模块就不会被引入
import { Button } from './components'
//components当中除了Button以外的模块都不会被引入
要确保你的代码没有反作用
// 反作用模块
import './extend'
在extend当中为number原型添加了一个方法,这样就会致使这个方法是没有被引入的
解决办法
"sideEffects": [
"./src/extend.js",
"*.css"
]
在package.json文件中
3五、代码分割
并非每一个模块都是在启动时须要的
因此须要实现按需加载
可是也不能分的太细
同域并行请求
请求头占资源
两种解决方法
①多入口打包
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)
3六、minicss
module: { rules: [ { test: /\.css$/, use: [ // 'style-loader', // 将样式经过 style 标签注入 MiniCssExtractPlugin.loader, 'css-loader' ] } ] },
3七、OptimizeCssAssetsWebpackPlugin
optimization: { //配置在这里能够保证只有压缩功能开启时才会执行下面插件 //因此须要本身手动保证一下js的压缩 minimizer: [ new TerserWebpackPlugin(), new OptimizeCssAssetsWebpackPlugin() 压缩样式文件 ] },
3八、输出文件名hash
output: { filename: '[name]-[contenthash:8].bundle.js' },
经过hash值控制缓存