问:为什么需要 webpak?
场景:在打包工具出现之前,假设一个HTML文件中需要引入很多外部文件,且由于js的单线程处理,相互依赖的文件还不能颠倒,否则会引起项目的崩溃,这么开发会有很大的负担,代码也很难扩展
然后,我们将这些文件按照一定的顺序合并到一个文件中去
虽然它解决了加载多个 js 文件的问题,但是可能会导致很多其他的问题:
1. 没有打包工具出现的问题
① 作用域问题
使用 jQuery,lodash,bootstrap 等库的时候,它们会给 windows 对象上绑定全局的变量,如:jQuery 会给 windows 上绑定 $(window.$xxx),lodash 会绑定下划线 _ (window._xxx)。互联网自己的文件可能也会在全局上面绑定自己的变量,这些变量会严重的污染 windows 对象,使 window 对象变得臃肿
② 文件太大的问题
如上:如果一个HTML中的多个 js 文件分散加载,那么页面的内容会随着文件的加载而逐渐显现出来。但如果将这多个文件合成一个 js 文件,那么这个脚本将会带来网络瓶颈,用户得等待一段时间才能看到内容,会有短暂的白屏,用户体验差
③ 可读性差、可维护性差
如果我们将所有代码都合并在一个超大的文件里,那么这对文件的可读性以及可维护性将会是灾难
2. 早期解决作用域问题的方法
Grunt、Gulp 解决作用域问题的方法:
早期我们用任务执行器—Grunt、Gulp来管理项目资源,这两个工具是将所有的项目文件拼接在一起,原理是利用了 JS 的立即调用函数表达式(IIFE),解决了大型项目作用域问题,当脚本封装在 IIFE 内部的时候,可以安全的拼接和组合所有的文件,而不必担心作用域冲突问题
什么是 IIFE ?
// 写个 ; 表示在压缩代码的时候不会有问题;(function() {var sub = '前端'})()// console.log(sub)// sub is not defined
上述代码显示:当函数变成一个立即调用的表达式以后,表达式的变量是不能在外部访问的,即不会污染 window 环境,也就解决了作用域问题
当然也可以在外部获取作用域内暴露的内容
const res = (function() {var sub = '前端'return sub})()console.log(res)
如此:既解决了作用域问题,又可以在外部获取作用域内想暴露的内容
新的问题:若只修改一个文件(超大文件)中的部分代码,则整个文件都要重新编译。所以我们需要一些方法将这个超大文件拆成一个个方法的模块
3. 解决代码拆分的方法
解决方法:node.js 中引入了一个叫 require 的机制,它允许我们在当前的文件中去加载和使用某个模块,导入需要的每个模块,这个开箱即用的功能帮助我们解决了代码的拆分问题
例:
math.js
const add = (a, b) =>{return a + b}const minus = (a, b) => {return a - b}module.exports = {add,minus}
serve.js
// 将 math.js 当做模块引入const math = require('./math')console.log(math.add(1, 2), math.minus(9, 2))
但 CommonJS 没有浏览器支持。没有 live binding(实时绑定)。循环引用存在问题。同步执行的模块解析加载器速度很慢。虽然 CommonJS 是 Node.js 项目的绝佳解决方案,但浏览器不支持模块,似乎又遇到了新问题
<script src="./commonJS/serve.js"></script>// Uncaught ReferenceError: require is not defined
4. 让浏览器支持模块的方法
在早期,能够使用 browserify 或者 requirejs 这样的打包工具来编写能够在浏览器中运行 commonjs 的模块的代码
例:使用 requirejs 来编写代码,它提供了 define 方法暴露模块,require 方法引入模块
- define 方法有两个参数:
- 第一个参数是一个数组,它定义了当前模块依赖的其他模块;
- 第二个参数是一个回调函数,这个回调函数是这个模块对外暴露的接口,函数体的返回值为当前模块需要暴露出去的内容
- require 方法有两个参数:
- 第一个参数是一个数组,它定义了当前模块依赖的其他模块;
- 第二个参数是一个回调函数,这个回调函数的参数为引入模块中暴露的内容
① 先定义两个模块:add.js minus.js
// add.jsconst add = (a, b) => {return a + b}define([], function() {return add});// minus.jsconst minus = (a, b) => {return a - b}define([], function() {return minus});
② 在使用 requireJS 之前需要在html文件上引入 requirejs 的包,并设置 requirejs 的入口文件(即设置属性 data-main 的路径)
<scriptsrc="https://cdn.bootcdn.net/ajax/libs/require.js/2.3.6/require.js"data-main="./requireJS/main.js"></script>
③ main.js
require(['./add', './minus'], function(add, minus) {let sum = add(2, 5)let diff = minus(8, 5)console.log(sum, diff)})
这样就可以在浏览器的控制台上打印结果了,这就是早期拆分模块的方法
但现在:模块已经成为 ECMAScript 官方的一个标准,它用 export default 导出模块,用 import xxx from ‘模块路径’ 导入模块
例:
① 先定义一个模块
// math.jsconst add = (a, b) => {return a + b}const minus = (a, b) => {return a - b}export default {add, minus}
② 在 html 上引入并使用该模块
两个注意点:
- 要在 script 标签上加入 type=’module’,否则报错:Cannot use import statement outside a module
在终端上输入 npx http-server 命令,开启服务。(不开启服务会产生跨域),然后在浏览器上输入网址即可访问
<script type="module">import math from './ES/math.js'console.log(math.add(1,2), math.minus(9, 4))</script>
ECMAScript 的缺点:浏览器对它的支持还不是很完整,版本迭代也不够快
答: webpack 解决以上所有问题
webpack 不仅可以让我们编写模块,而且还支持任何模块格式(同时支持 es6 和 commonjs ),并且可以同时处理 resource 和 assets。它是一个工具,可以打包你的 JavaScript 应用程序(支持 ESM 和 CommonJS),可以扩展为支持许多不同的静态资源,例如:images, fonts 和 stylesheets。
webpack 关心性能和加载时间;它始终在改进或添加新功能,例如:异步地加载和预先加载代码文件,以便为你的项目和用户提供最佳体验。 
webpack 是前端的一个项目构建工具,它是基于 Node.js 开发出来的一个前端工具;
借助于webpack这个前端自动化构建工具,可以完美实现资源的合并、打包、压缩、混淆等诸多功能
在没有使用webpack之前:举个例子:index.html里面有一大堆的css和js文件,如a.js b.js c.js d.js等等(1)a.js要用到b.js里面的一个函数,则a.js要放在b.js后面(2)c.js要用到a.js里面的一个函数,则c.js要放在a.js后面(3)b.js又要用到d.js文件里面的函数,则b.js就要放在d.js后面如果有N多个js文件,需要手动处理他们的关系,即容易出错。使用webpack:webpack的理念就是一切皆模块化,把一堆的css文件和js文件放在一个总的入口文件,通过require引入,剩下的事情webpack会处理,包括所有模块的前后依赖关系,打包、压缩、合并成一个js文件,公共代码抽离成一个js文件、某些自己指定的js单独打包,模块可以是css/js/images/font等等。
