webpack打包

webpack打包

打包工具

模块化解决了我们开发过程中的代码组织问题
但是模块化依然存在一些问题
1、EsModules存在环境兼容问题
2、模块文件比较多,网络请求频繁
3、所有的前端资源都需要模块化
所以模块化是必要的,但是也要解决对生产环境的影响
需要一个工具编译对应的代码,转换为兼容绝大多数环境的代码

在这里插入图片描述
将散落的文件打包到一起,解决模块文件频繁请求的问题
在这里插入图片描述
至于模块化的文件划分,只在开发阶段需要,可以更好的帮我们组织代码
运行环境没有必要,开发阶段通过模块编写,生产中打包到一个文件

至于其他的文件当作一个模块去使用,对于前端来说有一个统一的方案去使用

在这里插入图片描述
在这里插入图片描述

模块打包工具

webpack parcel rollup
web作为模块打包器解决js打包的问题,将零散的模块代码,打包到同一个js文件下,对于有文件兼容的代码,通过加载器(loader)对其进行编译转换,特殊类型资源的加载,例如加载样式、图片
通过 Plugin 实现各种自动化的构建任务,例如自动压缩
还具备代码拆分的能力(Code splitting),按照我们的需要进行打包,不用担心把所有的文件代码打包到一起,导致产生的文件比较大的问题。将首次加载所需要的代码打包到一起,然后将其他的模块进行单独存放,等到应用工作过程中,我们实际需要到哪个模块,再异步加载某个模块,实现增量加载或者渐进式加载。这样就不用担心文件太碎或者文件太大的问题。
资源模块:支持js中以模块化的方式去载入任意类型的资源文件,
打包工具解决的是整个前端的模块化,不单指js模块化,更好的享受模块化带来的优势

webpack使用

上手

解决前端模块化的方案
webpack是一个基于npm的工具模块,生成package.json,
yarn init --yes
安装webpack对应的核心模块以及对应的cli模块
sudo yarn add webpack webpack-cli --dev
查看webpack对应的版本
yarn webpack --version
打包
yarn webpack
package.json 修改打包命令
“scripts”: {
“build”:“webpack”
},

配置文件

新建文件webpack.config.js
运行在node环境中的js文件,按照CommonJs的方式编写代码

const path  = require('path')
module.exports = {
	entry:'./src/main.js',
	output:{
		filename:'bundle.js',
		//指定文件输出路径,但是必须是个绝对路径
		path:path.join(__dirname, 'output')
	}
}

工作模式

webpack4新增了工作模式的配置,针对于不同环境的预设配置

打包时如果默认没有进行mode的属性,则是默认为production,在这环境下webpack会自动启动一些优化插件,比如说进行代码的压缩,但是打包的结果没办法阅读

在这里插入图片描述
或者使用webpack-cli进行取值配置启动,
yarn webpack --mode development//优化打包的速度,添加辅助打包东西
yarn webpack --mode none//打包不做任何操作
或者添加mode属性

const path  = require('path')
module.exports = {
    mode:'development',
	entry:'./src/index.js',
	output:{
		filename:'bundle.js',
		//指定文件输出路径,但是必须是个绝对路径
		path:path.join(__dirname, 'output')
	}
}

打包结果运行原理

将 Webpack 工作模式设置为 none,这样 Webpack 就会按照最原始的状态进行打包,所得到的代码更容易理解和阅读。(VSCode 中折叠代码的快捷键是 Ctrl + K,Ctrl + 0 )

const path  = require('path')
module.exports = {
    mode:'none',
	entry:'./src/index.js',
	output:{
		filename:'bundle.js',
		//指定文件输出路径,但是必须是个绝对路径
		path:path.join(__dirname, 'output')
	}
}

整体生成的代码其实就是一个立即执行函数,这个函数是 Webpack 工作入口(webpackBootstrap),它接收一个 modules 参数,调用时传入了一个数组。
在这里插入图片描述
展开这个数组,里面元素的函数对应的就是我们源代码中的模块,也就是说每个模块最终被包裹到了这样一个函数中,从而实现模块私有作用域,数组中每个元素都是一个参数列表相同的函数

/******/ (function(modules) { // webpackBootstrap
/******/ 	// 使用一个对象来缓存所有的模块,这样使的不会重复导入模块
/******/ 	var installedModules = {};
/******/
/******/ 	// 请求函数
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		//  如果缓存里面导入过该模块,那么不需要在重新导入模块,返回改模块导出的结果
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// 创建一个新模块(并将其放入缓存
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// 执行模块功能
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// 将模块标记为已加载
/******/ 		module.l = true;
/******/
/******/ 		// 返回模块的导出
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// 暴露modules对象
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// 公开模块缓存
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// 定义协调导出的getter函数
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ 		}
/******/ 	};
/******/
/******/ 	// 在导出上定义
/******/ 	__webpack_require__.r = function(exports) {
/******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ 		}
/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
/******/ 	};
/******/
/******/ 	// 创建假命名空间对象
/******/ 	// mode & 1: 值是模块id,需要它
/******/ 	// mode & 2: 将值的所有属性合并到ns中
/******/ 	// mode & 4: 已存在ns对象时返回值
/******/ 	// mode & 8|1: 表现得像要求的那样
/******/ 	__webpack_require__.t = function(value, mode) {
/******/ 		if(mode & 1) value = __webpack_require__(value);
/******/ 		if(mode & 8) return value;
/******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ 		var ns = Object.create(null);
/******/ 		__webpack_require__.r(ns);
/******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ 		return ns;
/******/ 	};
/******/
/******/ 	// getDefaultExport函数与非协调模块兼容
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/
/******/ 	// 加载输入模块并返回导出
/******/ 	return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _heading_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);


const heading = Object(_heading_js__WEBPACK_IMPORTED_MODULE_0__["default"])()

document.body.append(heading)


/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = (() => {
  const element = document.createElement('h2')

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

  return element
});


/***/ })
/******/ ]);

最开始定义了一个 installedModules 对象用于存放或者缓存加载过的模块,然后定义了一个 require 函数(这个函数是用来加载模块的),再往后就是在 require 函数上挂载了一些其他的数据和工具函数。

这个函数执行到最后调用了 require 函数,传入的模块 id 为 0,开始加载模块。模块 id 实际上就是模块数组的元素下标,也就是说这里开始加载源代码中所谓的入口模块。

资源模块加载

webpack内部的loader只处理js文件,其他的文件解析需要引入对应的loader
在这里插入图片描述
安装css对应的loader
sudo yarn add css-loader style-loader --dev

const path = require('path')

module.exports = {
  mode:'none',
  entry: './src/main.css',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'output')
  },
  module:{
    rules:[
      {
        //用来匹配打包过程中对应的文件路径
        test: /.css$/,
        //使用结果从后向前执行
        use:[
          //将css生成的结果追加到页面上
          'style-loader',
          'css-loader',
        ]
      }
    ]
  }
}

导入资源模块

在这里插入图片描述

根据代码需要动态加载资源,需要资源的不是应用而是对应的代码,驱动整个前端应用
main.js

import './main.css'
const path = require('path')

module.exports = {
  mode:'none',
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'output')
  },
  module:{
    rules:[
      {
        //用来匹配打包过程中对应的文件路径
        test: /.css$/,
        //使用结果从后向前执行
        use:[
          //将css生成的结果追加到页面上
          'style-loader',
          'css-loader',
        ]
      }
    ]
  }
}

文件资源加载器

sudo yarn add file-loader --dev
在这里插入图片描述
import icon from ‘./icon.png’
const img = new Image()
img.src = icon
document.body.append(img)

const path = require('path')

module.exports = {
  mode:'none',
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'output'),
    //告诉浏览器打包生成文件的路径
    publicPath:'dist/'
  },
  module:{
    rules:[
      {
        //用来匹配打包过程中对应的文件路径
        test: /.css$/,
        //使用结果从后向前执行
        use:[
          //将css生成的结果追加到页面上
          'style-loader',
          'css-loader',
        ]
      },
      {
        //用来匹配打包过程中对应的文件路径
        test: /.png$/,
        //使用结果从后向前执行
        use:[
          'file-loader'
        ]
      },
    ]
  }
}

data url,url加载器

sudo yarn add url-loader --dev
将文件转换为url
在这里插入图片描述
小文件使用data-urls,减少请求次数
大文件单独提取存放,提高加载速度

const path = require('path')

module.exports = {
  mode:'none',
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'output'),
    //告诉浏览器打包生成文件的路径
    publicPath:'dist/'
  },
  module:{
    rules:[
      {
        //用来匹配打包过程中对应的文件路径
        test: /.css$/,
        //使用结果从后向前执行
        use:[
          //将css生成的结果追加到页面上
          'style-loader',
          'css-loader',
        ]
      },
      {
        //用来匹配打包过程中对应的文件路径
        test: /.png$/,
        //使用结果从后向前执行
        use:{
          loader: 'file-loader',
          options: {
            limit:10 * 1024
          }
        }
      },
    ]
  }
}

加载器分类

编译转换类 css-loader
文件操作类 file-loader
代码检查类 eslint-loader

转换ES6代码

webpack可以处理import和export
sudo yarn add babel-loader @babel/core @babel/preset-env --dev

const path = require('path')

module.exports = {
  mode:'none',
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'output'),
    //告诉浏览器打包生成文件的路径
    publicPath:'dist/'
  },
  module:{
    rules:[
      {
        //用来匹配打包过程中对应的文件路径
        test: /.js$/,
        //使用结果从后向前执行
        use:{
          loader: 'babel-loader',
          options: {
            presets:['@babel/preset-env']
          }
        }
      },
      {
        //用来匹配打包过程中对应的文件路径
        test: /.css$/,
        //使用结果从后向前执行
        use:[
          //将css生成的结果追加到页面上
          'style-loader',
          'css-loader',
        ]
      },
      {
        //用来匹配打包过程中对应的文件路径
        test: /.png$/,
        //使用结果从后向前执行
        use:{
          loader: 'file-loader',
          options: {
            limit:10 * 1024
          }
        }
      },
    ]
  }
}

加载资源的方式

loader加载的非js也会触发资源加载
样式代码中的@import指令和url函数
html代码中图片标签的src属性
sudo yarn add html-loader -dev
html-loader 默认只会处理img标签下的src属性

const path = require('path')

module.exports = {
  mode:'none',
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'output'),
    //告诉浏览器打包生成文件的路径
    publicPath:'dist/'
  },
  module:{
    rules:[
      {
        //用来匹配打包过程中对应的文件路径
        test: /.js$/,
        //使用结果从后向前执行
        use:{
          loader: 'babel-loader',
          options: {
            presets:['@babel/preset-env']
          }
        }
      },
      {
        //用来匹配打包过程中对应的文件路径
        test: /.css$/,
        //使用结果从后向前执行
        use:[
          //将css生成的结果追加到页面上
          'style-loader',
          'css-loader',
        ]
      },
      {
        //用来匹配打包过程中对应的文件路径
        test: /.png$/,
        //使用结果从后向前执行
        use:{
          loader: 'file-loader',
          options: {
            limit:10 * 1024
          }
        }
      },
      {
        test: /.html$/,
        use:{
          loader: 'html-loader',
          options:{
            attrs:['img:src', 'a:href']
          }
        }
      },
    ]
  }
}

核心工作原理

loader机制是webpack打包的核心
在这里插入图片描述

插件机制

是为了增强,webpack在项目自动化方面的能力
plugin解决资源加载外的其他自动化工作
清除dist目录,拷贝静态资源到输出目录,压缩代码,

自动清除输出目录插件

sudo yarn add clean-webpack-plugin --dev

const path = require('path')
const {CleanWebpackPlugin} = require('clean-webpack-plugin') 

module.exports = {
  mode:'none',
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'output'),
    //告诉浏览器打包生成文件的路径
    publicPath:'dist/'
  },
  module:{
    rules:[
      {
        //用来匹配打包过程中对应的文件路径
        test: /.js$/,
        //使用结果从后向前执行
        use:{
          loader: 'babel-loader',
          options: {
            presets:['@babel/preset-env']
          }
        }
      },
      {
        //用来匹配打包过程中对应的文件路径
        test: /.css$/,
        //使用结果从后向前执行
        use:[
          //将css生成的结果追加到页面上
          'style-loader',
          'css-loader',
        ]
      },
      {
        //用来匹配打包过程中对应的文件路径
        test: /.png$/,
        //使用结果从后向前执行
        use:{
          loader: 'file-loader',
          options: {
            limit:10 * 1024
          }
        }
      },
      {
        test: /.html$/,
        use:{
          loader: 'html-loader',
          options:{
            attrs:['img:src', 'a:href']
          }
        }
      },
    ]
  },
  plugins:[
    //创建实例放入数组中
    new CleanWebpackPlugin()
  ]
}

自动生成html插件

自动生成使用bundle.js的HTML,通过webpack输出html文件,确保打包出来的文件路径的正确性 html-webpack-plugin,自动生成index.html
sudo yarn add html-webpack-plugin --dev
默认导出插件的类型,不需要解构,

const path = require('path')
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, 'output'),
    //告诉浏览器打包生成文件的路径
    // publicPath:'dist/'
  },
  module:{
    rules:[
      {
        //用来匹配打包过程中对应的文件路径
        test: /.js$/,
        //使用结果从后向前执行
        use:{
          loader: 'babel-loader',
          options: {
            presets:['@babel/preset-env']
          }
        }
      },
      {
        //用来匹配打包过程中对应的文件路径
        test: /.css$/,
        //使用结果从后向前执行
        use:[
          //将css生成的结果追加到页面上
          'style-loader',
          'css-loader',
        ]
      },
      {
        //用来匹配打包过程中对应的文件路径
        test: /.png$/,
        //使用结果从后向前执行
        use:{
          loader: 'file-loader',
          options: {
            limit:10 * 1024
          }
        }
      },
      {
        test: /.html$/,
        use:{
          loader: 'html-loader',
          options:{
            attrs:['img:src', 'a:href']
          }
        }
      },
    ]
  },
  plugins:[
    //创建实例放入数组中
    new CleanWebpackPlugin(),
    //自动生成使用bundle.js的HTML
    new htmlWebpackPlugin({
      //生成dist文件夹下的index.html文件的属性配置
      title:'webpackPlugin',
      meta:{
        viewreport:'width=device-width'
      },
      //生成的文件模板地址,可以自定义
      template:''
    }),
    //创建额外的html文件
    new htmlWebpackPlugin({
      filename:'about.html'
    })
  ]
}

插件实现的原理

通过在生命周期中挂载函数实现扩展

自动编译

watch工作模式,监听文件变化,自动重新打包,
以监视模式进行,
sudo yarn webpack --watch

webpack dev server

集成自动编译和自动刷新浏览器
sudo yarn add webpack-dev-serve --dev
yarn webpack-dev-serve --open//自动唤醒浏览器
支持配置代理
开发阶段

devServer:{
    proxy:{
      '/api':{
        target:'https://api.github.com',
        //将api替换为空
        pathRewrite:{
          '^api':''
        },
        //不能使用localhost:8080,作为github的主机名
        changeOrigin:true
      }
    },
  },

sourceMap

源代码地图,用于映射转换过后的代码与源代码之间的关系,主要是开发人员调试

devtool:'source-map',

HMR

hot module replacement
模块热替换
webpack-dev-serve --hot开启
也可以通过配置文件开启

devServer:{
	hot:true
}

样式文件可以自动处理,更新过后可以及时替换,但是脚本文件没有规律,使用起来各不相同
脚本文件需要手动处理模块热替换逻辑

vue-cli 没有手动处理,但是脚本代码有一定的规律,都有通用的替换方法,内部集成了HMR方案
api:

module.hot.accept('./editor', ()=>{
	console.log('edit模块更新了')
})

definePlugin

注入process.env.NODE_ENV

Tree Shaking

去除冗余代码
是一种功能搭配后的优化效果

optimization:{
	//只导出外部使用的成员,比较代码
	usedExports:true,
	//尽可能将所有的模块合并到一起,输出到一个函数中,提升了运行效率,减少了输出代码的体积
	concatenateModules:true,
	//删除代码
	minimize:true
}

最新版本的commonjs不会使Tree Shaking失效

sideEffects

标记代码是否有副作用,从而为Tree Shaking提供更大的压缩空间
副作用指的是除了导出成员之外的事情
一般用于开发npm包标记是否有副作用

代码分割

Code Splitting
不是每个模块在启动时都是必要的
分包,按需加载
目前主流的http1.1版本
同域并行请求限制
每次请求都有一定的延迟
大量的header浪费带宽流量
所以出现了新的解决方案
多入口打包,输出多个打包结果,

const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'none',
  entry: {
    index: './src/index.js',
    album: './src/album.js'
  },
  output: {
    filename: '[name].bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      }
    ]
  },
  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'
    }
  },

Rollup

对比与webpack来说更加小巧,仅仅是一款ESM的打包器,没有任何其他额外的功能
不支持类似于HMR这种的高级特性。充分利用ESM的各种特性的高效打包器
sudo yarn add rollup --dev
//yarn能够自动找到node_modules下的cli信息
yarn rollup//运行
yarn rollup ./src/index.js//找到打包入口文件
yarn rollup ./src/index.js --format//指定输出格式 iife//自调用格式 --file dist/bundle.js//指定输出文件的路径
rollup打包结果只会保留用到的部分,默认开启tree shaking

配置文件

新建rollup.config.js文件

export default {
    input:'src/index.js',
    output:{
        file:'dist/bundle.js',
        format:'iife'
    }

}

使用插件

插件是rollup唯一的扩展路径
rollup-plugin-json

//默认导出的是插件函数
import json from 'rollup-plugin-json'
export default {
    input:'src/index.js',
    output:{
        file:'dist/bundle.js',
        format:'iife'
    },
    //将函数的调用结果配置到数组中
    plugins:[
        json()
    ]
}

加载npm模块

不想webpack一样导入模块名称从而引用模块
需要辅助插件
sudo yarn add rollup-plugin-node-resolve --dev

加载CommonJs模块

sudo yarn add rollup-plugin-commonjs --dev

新建cjs_module.js

module.exports = {
    foo:'bar'
}
//默认导出的是插件函数
import json from 'rollup-plugin-json'
import resolve from 'rollup-plugin-node-resolve'
import common from 'rollup-plugin-commonjs'
export default {
    input:'src/index.js',
    output:{
        file:'dist/bundle.js',
        format:'iife'
    },
    //将函数的调用结果配置到数组中
    plugins:[
        json(),
        resolve(),
        common()
    ]

}
// 导入模块成员
import { log } from './logger'
import messages from './messages'
import cjs from './cjs_module'
// 使用模块成员
const msg = messages.hi

log(msg)
log(cjs)

代码拆分是通过动态导入
优势:输出的结果更加扁平,执行效率也会更高,自动移除未引用的代码
缺点:加载非ESM的第三方模块比较复杂,
模块最终会被打包到一个函数中,无法实现HMR
浏览器的代码拆分需要以来AMD的库

parcel零配置打包器

sudo yarn add parcel-bundler --dev
以html作为打包文件入口
yarn parcel build src/index.html

一些插件可以自动引入,不要自己手动命令行输入,保存之后自动刷新页面

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值