赞
踩
代码转换、文件优化、代码分割、模块合并、自动刷新、代码校验、自动发布
webpack
的基本配置webpack
的高级配置webpack
的优化策略ast
抽象语法树webpack
的Tapable
webpack
的流程 手写webpack
webpack
中常见的loader
webpack
中常见的plugin
webpack
webpack
:提供了内置的东西 express pluginwebpack-cli
: npx webpackwebpack-dev-server
:启动服务 proxy beforeapp
npx webpack-dev-server
- let path = require("path");
- let HtmlWebpackPlugin = require("html-webpack-plugin");
- module.exports = {//webpack 是node中的一个模块 CommonJs
- devServer: {//静态服务器的配置
- port: 3000,
- progress: true,//进度提哦啊
- contentBase: "./dist",//静态资源路径
- compress:true//是否压缩Gzip
- },
- mode: "production",//环境
- entry: "./src/index.js",
- output: {
- filename: "bundle[hash:8].js",//设置hash之后会解决浏览器缓存问题
- path: path.resolve(__dirname, "dist")//解析 会把相对路径解析成绝对路径
- },
- plugins: [
- new HtmlWebpackPlugin({//打包的时候 自动把html打包到dist目录
- template: "./src/index.html",
- filename: "index.html",
- minify:{
- removeAttributeQuotes:true,//去除双引号
- collapseWhitespace:true//单行压缩
- },
- hash:true//是否加hash后缀
- })
- ]
- };
-
html
文件- plugins:[
- new HtmlWebpackPlugin({
- template: './src/index.html',
- filename: 'index.html',
- minify: {
- collapseWhitespace: true,
- removeAttributeQuotes: true
- },
- hash: true
- })
- ]
2.2.1 loader配置
如果直接插入css
文件会报一个这样的错误
解决: 下载两个loader
- module: {//模块
- rules: [//规则
- {
- test: /\.css$/,
- use: [{
- loader: 'style-loader',//将css插入到head中
- options: {
- insert: 'top'//head/top foot
- }
- }, 'css-loader']
- },
- {
- test: /\.scss$/,
- use: ['style-loader','css-loader', 'sass-loader']
- }
- ],
- },
2.1.1 分离css
但是 此时 我们打包后发现 css
是插入在js
里面的
为了解决这个问题 接下来我们引入 mini-css-extract-plugin
这个插件
- let MiniCssExtractPlugin require('mini-css-extract-plugin')
-
- rules: [
- {
- test: /\.css$/,
- use: [{
- loader: MiniCssExtractPlugin.loader,
- }, 'css-loader']//loader顺序的规律
- },
- {
- test: /\.(sc|sa)ss$/,
- use: [{
- loader: MiniCssExtractPlugin.loader,
- }, 'css-loader', 'sass-loader']//loader顺序的规律
- }
- ]
当我们加入css3
之后 新的问题出现了 没有前缀
2.1.3 引入前缀
此时 我们需要下载一个包autoprefixer
以及一个loader
文件postcss-loader
- {
- test: /\.css$/,
- use: [{
- loader: MiniCssExtractPlugin.loader,
- }, 'css-loader','postcss-loader']//loader顺序的规律
- },
postcss.config.js
- module.exports = {
- plugins: [require('autoprefixer')]
- };
再次打包
需要注意的是 此设置项只能用早生产环境
mode: 'production',
2.1.4 压缩css
文件
如何压缩文件呢
其中有个包 optimize-css-assets-webpack-plugin
此包主要是用来压缩css
的 但是 引入这个包后出现了js
没被压缩的问题
怎么解决呢
按照官网配置需要使用TerserJSPlugin
https://www.npmjs.com/package/mini-css-extract-plugin
- optimization: {//webpack4.0之后新出的优化项配置
- minimizer: [new TerserJSPlugin({}), new OptimizeCssAssetsPlugin({})]
- },
TerserJSPlugin
具体参数查看这个
- interface TerserPluginOptions {
- test?: string | RegExp | Array<string | RegExp>;
- include?: string | RegExp | Array<string | RegExp>;
- exclude?: string | RegExp | Array<string | RegExp>;
- chunkFilter?: (chunk: webpack.compilation.Chunk) => boolean;
- cache?: boolean | string;
- cacheKeys?: (defaultCacheKeys: any, file: any) => object;
- parallel?: boolean | number;
- sourceMap?: boolean;
- minify?: (file: any, sourceMap: any) => MinifyResult;
- terserOptions?: MinifyOptions;
- extractComments?: boolean
- | string
- | RegExp
- | ExtractCommentFn
- | ExtractCommentOptions;
- warningsFilter?: (warning: any, source: any) => boolean;
- }
js
文件2.2.1 babel核心模块
当我们尝试对写了es6
语法的代码进行打包时候
并没有变成es5
接下来执行命令 babel
yarn add babel-loader @babel/core @babel/preset-env
babel-loader
: babel
加载器@babel/core
:babel
的核心模块@babel/preset-env
: 将es6
转换成es5
@babel/plugin-transform-runtime
@babel/runtime
@babel/polyfill
- {
- test: /\.js$/,
- use: [
- {
- loader: 'babel-loader',
- options: {//预设
- presets: ['@babel/preset-env']
- }
- }
- ]
- }
接下来 就是见证奇迹的时刻
2.2.2 处理箭头函数
@babel/preset-env
2.2.3 处理装饰器
当我们添加装饰器 会有如下提示
具体可以查看官网 https://babeljs.io/docs/en/babel-plugin-proposal-decorators
- {
- test: /\.js$/,
- use: [
- {
- loader: 'babel-loader',
- options: {//预设
- presets: ['@babel/preset-env'],
- plugins:[
- ["@babel/plugin-proposal-decorators", { "legacy": true }],
- ["@babel/plugin-proposal-class-properties", { "loose" : true }]
- ]
- }
- }
- ]
- },
-
index.js
- @log
- class A {
- a = 1;//es7 的语法(es6的变种语法) // let a = new A() a.a = 1
- }
- function log(target) {
- console.log(target,'21');
- }
-
2.2.4 处理es7
语法
- {
- test: /\.js$/,
- use: [
- {
- loader: 'babel-loader',
- options: {//预设
- presets: ['@babel/preset-env'],
- plugins:['@babel/plugin-proposal-class-properties']
- }
- }
- ]
- }
a.js
- class B {
-
- }
-
- function* fnB() {
- yield 1;
- }
-
- console.log(fnB().next());
-
- module.exports = 'a';
接下来打包发现 每个文件都会打包一个_classCallCheck
写了generator运行也会报错
出现以上问题的原因是
在webpack
运行时不会自动检测哪些方法重用了
es6
的高级语法 比如generator和promise不会转换成es5
根据官方文档https://babeljs.io/docs/en/babel-plugin-transform-runtime#docsNav
需要下载两个包
yarn add @babel/plugin-transform-runtime @babel/runtime -D
执行npx webpack
但是 报了一些警告
- {
- test: /\.js$/,
- use: [
- {
- loader: 'babel-loader',
- options: {//预设
- presets: ['@babel/preset-env'],
- plugins: [
- ["@babel/plugin-proposal-decorators", {"legacy": true}],
- ["@babel/plugin-proposal-class-properties", {"loose": true}],
- "@babel/plugin-transform-runtime"
- ]
- }
- }
- ],
- include: path.resolve(__dirname, 'src'),
- exclude: /node_modules/
-
- },
2.2.5 处理全局变量的问题
方法一 : 外置loader
require('expose-loader?$!jquery');
1
方法二 : 内置loader
在每个模块都注入$
- // rules:
-
- {//内置loader
- test: require.resolve('jquery'),
- use: 'expose-loader?$'
- },
-
- // plugins:
-
- //提供者
- new webpack.ProvidePlugin({
- "$": "jquery"
- })
优化:
如果在html
引入cdn
路径并且在页面也 import $ from jquery
这就坏了, 即使引入cdn也会打包
- //排除之外 加入 在cdn引入了这个包 就不会打包这个包
- externals: {
- 'jquery': '$
- }
2.3.1 处理js
中的图片
- index.js
-
- import logo from './logo.png';
- <img src=logo/>
-
-
- webpack.config.js:
- {
- test: /\.(png|jpg|gif)$/,
- use: [{
- loader: 'file-loader',
- options: {
- esModule: false,
- },
- }
- }
-
2.3.2 处理css
中图片文件
因为css-loader
中已经对图片做loader
处理了 所以 只需要引入相应路径就行了
2.3.3 处理html
中的图片
- //1. 下载依赖
- yarn add html-withimg-plugin -D
-
- //2. 配置
- {
- test:/\.html$/,
- use:['html-withimg-plugin']
-
- }
-
- let {CleanWebpackPlugin} = require('clean-webpack-plugin');
-
- //使用:
- plugins:[
- new CleanWebpackPlugin()
- ]
- const CopyPlugin = require('copy-webpack-plugin');
-
- module.exports = {
- plugins: [
- new CopyPlugin([
- { from: 'source', to: 'dest' },
- { from: 'other', to: 'public' },
- ]),
- ],
- };
2.6.1 resolve
- resolve:{
- modules:[path.resolve(__dirname,'node_modules')],//只从当前这个node_modules查找相应的包
- alise:{//别名
- "bootstrapcss":"bootstrap/dist/css/bootstrap.css"
- },
- extensions:['js','jsx','vue','json','css']
- }
2.6.2 分离文件 dev、 prod、base
- let {smart} = require('webpack-merge')
- let base = require('./webpack.config.js')
-
- module.exports = smart(base,{
- mode:'production'
- })
- devServer: {
- port: 8080,
- host: '0.0.0.0',
- quiet: true,
- proxy: {
- // '/api': 'http://127.0.0.1:3000',
- '/api': {
- target: 'http://127.0.0.1:3000',
- pathRewrite:{
- '^/api': ''
- }
- },
-
- },
- before(app) {
- //app就是express对象
- app.get('/list', function (req, res) {
- res.send({code: 1, msg: 'hello'});
- });
- }
- },
//1: npm i webpack-dev-middleware
- let middleDevWebpack = require('webpack-dev-middleware')
- let config = require('./webpack.config.js')
-
- app.use(middleDevWebpack(config))
-
热更新
- devServer:{
- hot:true,
- quite:true//安静启动
- }
实时监听
- watch:true,
- wathcOptions:{
- poll:1000,
- aggregateTimeout:500,
- ignore:/note_modules/
- }
打包优化,可以从几个出发点点
打包体积
加载速度
打包速度
webpack自带优化
优化网络解析时长和执行时长
优化webpack产出
webpack 知识点
必杀技--动态链接库
多进程打包之HappyPack
提取公共代码
tree-sharking
scope-hoisting
需要用到happypack
实现多线程打包
注意: 如果体积较小会使打包时间更长
第一步:下载
npm install happypack --save-dev
- const HappyPack = require('happypack');
-
- module.exports = {
- ...
- }
第二步: 将常用的 loader
替换为 happypack/loader
- const HappyPack = require('happypack');
-
- module.exports = {
- ...
- module: {
- rules: [
- test: /\.js$/,
- // use: ['babel-loader?cacheDirectory'] 之前是使用这种方式直接使用 loader
- // 现在用下面的方式替换成 happypack/loader,并使用 id 指定创建的 HappyPack 插件
- use: ['happypack/loader?id=babel'],
- // 排除 node_modules 目录下的文件
- exclude: /node_modules/
- ]
- }
- }
三、创建 HappyPack 插件
- module.exports = {
- ...
- module: {
- rules: [
- test: /\.js$/,
- // use: ['babel-loader?cacheDirectory'] 之前是使用这种方式直接使用 loader
- // 现在用下面的方式替换成 happypack/loader,并使用 id 指定创建的 HappyPack 插件
- use: ['happypack/loader?id=babel'],
- // 排除 node_modules 目录下的文件
- exclude: /node_modules/
- ]
- },
- plugins: [
- ...,
- new HappyPack({
- /*
- * 必须配置
- */
- // id 标识符,要和 rules 中指定的 id 对应起来
- id: 'babel',
- // 需要使用的 loader,用法和 rules 中 Loader 配置一样
- // 可以直接是字符串,也可以是对象形式
- loaders: ['babel-loader?cacheDirectory']
- })
- ]
- }
有些包自带语言包,有时候不需要把所有的语言包跟着打包比如 moment
,那么我们就需要把这个包特殊对待,
主要是通过webpack
自导的IgnorePlugin
src下某.js
- import moment from 'moment';
- import 'moment/locale/zh-cn';
-
- moment.locale('zh-cn');
-
- let r = moment().endOf('day').fromNow();
- console.log(r);
webpack.config.js
- plugins: [
- ...
- new webpack.IgnorePlugin(/\.\/locale/,/moment/),
- ]
有些文件我们不希望打包,比如已经在cdn中引入了的文件,此时要用externals
进行配置
- modules:{
- noParse:/jquery/,
- ...
- }
-
- plugins: [
- ...
- new webpack.ProvidePlugin({
- '$': 'jquery'
- }),
- ]
-
- //忽略打包的文件
- externals:{
- 'jquery': '$'
- }
-
什么是动态链接库: 用dll链接的方式提取固定的js文件,并链接这个js文件
当我们引入一个js文件的时候,这个js文件比较大,那我们是否可以单独打包,发布到cdn上,直接引用
比如 当我们想要把react打包的时候,希望将react和reactdom放到一个js文件打包的时候 不打包这两个文件,而是直接引用js的cdn路径
新建一个webpack的js配置文件
webpack.react.js
- var path = require('path');
- let webpack = require("webpack");
-
- module.exports = {
- mode: 'development',
- entry: {
- react: ['react', 'react-dom']
- },
- output:{
- filename: '_dll_[name].js',
- path: path.resolve(__dirname, 'dist'),
- library: '_dll_[name]',
- // "var" | "assign" | "this" | "window" | "self" | "global" | "commonjs" | "commonjs2" | "commonjs-module" | "amd" | "amd-require" | "umd" | "umd2" | "jsonp" | "system"
- // libraryTarget: 'commonjs2'//默认 var
- },
- plugins: [
- new webpack.DllPlugin({
- name: '_dll_[name]',
- path: path.resolve(__dirname, 'dist', 'manifest.json')
- })
-
- ]
- };
npx webpack --config webpack.react.js
此时就会生成一个manifest.json文件
最后 在webpack.prod.config.js线上配置文件中引入插件
- new webpack.DllReferencePlugin({
- manifest: path.resolve(__dirname, 'dist', 'manifest.json')
- })
- optimization: {//webpack4.0之后出现的优化项
- minimizer: [new TerserPlugin({}), new OptimizeCssAssetsWebpackPlugin({})],//压缩css
- //缺陷 可以压缩css 但是 js压缩又出现了问题
-
- splitChunks:{//分割代码块
- cacheGroups:{//缓存组
- common:{//公共的逻辑
- chunks: 'initial',//从入口文件开始查找
- minSize: 0,//最小分包体积
- minChunks: 2,//
- },
- vendor:{
- priority: 1,
- test:/node_modules/,
- chunks: 'initial',
- minSize: 0,
- minChunks: 2
- }
- }
- }
- },
Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程 :
在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。
- class Compiler {
- constructor(options) {
- // webpack 配置
- const { entry, output } = options
- // 入口
- this.entry = entry
- // 出口
- this.output = output
- // 模块
- this.modules = []
- }
- // 构建启动
- run() {}
- // 重写 require函数,输出bundle
- generate() {}
- }
-
我们这里使用@babel/parser,这是 babel7 的工具,来帮助我们分析内部的语法,包括 es6,返回一个 AST 抽象语法树。
- // webpack.config.js
-
- const path = require('path')
- module.exports = {
- entry: './src/index.js',
- output: {
- path: path.resolve(__dirname, './dist'),
- filename: 'main.js'
- }
- }
- //
-
- const fs = require('fs')
- const parser = require('@babel/parser')
- const options = require('./webpack.config')
-
- const Parser = {
- getAst: path => {
- // 读取入口文件
- const content = fs.readFileSync(path, 'utf-8')
- // 将文件内容转为AST抽象语法树
- return parser.parse(content, {
- sourceType: 'module'
- })
- }
- }
-
- class Compiler {
- constructor(options) {
- // webpack 配置
- const { entry, output } = options
- // 入口
- this.entry = entry
- // 出口
- this.output = output
- // 模块
- this.modules = []
- }
- // 构建启动
- run() {
- const ast = Parser.getAst(this.entry)
- }
- // 重写 require函数,输出bundle
- generate() {}
- }
-
- new Compiler(options).run()
-
Babel 提供了@babel/traverse(遍历)方法维护这 AST 树的整体状态,我们这里使用它来帮我们找出依赖模块。
- const fs = require('fs')
- const path = require('path')
- const options = require('./webpack.config')
- const parser = require('@babel/parser')
- const traverse = require('@babel/traverse').default
-
- const Parser = {
- getAst: path => {
- // 读取入口文件
- const content = fs.readFileSync(path, 'utf-8')
- // 将文件内容转为AST抽象语法树
- return parser.parse(content, {
- sourceType: 'module'
- })
- },
- getDependecies: (ast, filename) => {
- const dependecies = {}
- // 遍历所有的 import 模块,存入dependecies
- traverse(ast, {
- // 类型为 ImportDeclaration 的 AST 节点 (即为import 语句)
- ImportDeclaration({ node }) {
- const dirname = path.dirname(filename)
- // 保存依赖模块路径,之后生成依赖关系图需要用到
- const filepath = './' + path.join(dirname, node.source.value)
- dependecies[node.source.value] = filepath
- }
- })
- return dependecies
- }
- }
-
- class Compiler {
- constructor(options) {
- // webpack 配置
- const { entry, output } = options
- // 入口
- this.entry = entry
- // 出口
- this.output = output
- // 模块
- this.modules = []
- }
- // 构建启动
- run() {
- const { getAst, getDependecies } = Parser
- const ast = getAst(this.entry)
- const dependecies = getDependecies(ast, this.entry)
- }
- // 重写 require函数,输出bundle
- generate() {}
- }
-
- new Compiler(options).run()
-
将 AST 语法树转换为浏览器可执行代码,我们这里使用@babel/core 和 @babel/preset-env。
- const fs = require('fs')
- const path = require('path')
- const options = require('./webpack.config')
- const parser = require('@babel/parser')
- const traverse = require('@babel/traverse').default
- const { transformFromAst } = require('@babel/core')
-
- const Parser = {
- getAst: path => {
- // 读取入口文件
- const content = fs.readFileSync(path, 'utf-8')
- // 将文件内容转为AST抽象语法树
- return parser.parse(content, {
- sourceType: 'module'
- })
- },
- getDependecies: (ast, filename) => {
- const dependecies = {}
- // 遍历所有的 import 模块,存入dependecies
- traverse(ast, {
- // 类型为 ImportDeclaration 的 AST 节点 (即为import 语句)
- ImportDeclaration({ node }) {
- const dirname = path.dirname(filename)
- // 保存依赖模块路径,之后生成依赖关系图需要用到
- const filepath = './' + path.join(dirname, node.source.value)
- dependecies[node.source.value] = filepath
- }
- })
- return dependecies
- },
- getCode: ast => {
- // AST转换为code
- const { code } = transformFromAst(ast, null, {
- presets: ['@babel/preset-env']
- })
- return code
- }
- }
-
- class Compiler {
- constructor(options) {
- // webpack 配置
- const { entry, output } = options
- // 入口
- this.entry = entry
- // 出口
- this.output = output
- // 模块
- this.modules = []
- }
- // 构建启动
- run() {
- const { getAst, getDependecies, getCode } = Parser
- const ast = getAst(this.entry)
- const dependecies = getDependecies(ast, this.entry)
- const code = getCode(ast)
- }
- // 重写 require函数,输出bundle
- generate() {}
- }
-
- new Compiler(options).run()
-
- const fs = require('fs')
- const path = require('path')
- const options = require('./webpack.config')
- const parser = require('@babel/parser')
- const traverse = require('@babel/traverse').default
- const { transformFromAst } = require('@babel/core')
-
- const Parser = {
- getAst: path => {
- // 读取入口文件
- const content = fs.readFileSync(path, 'utf-8')
- // 将文件内容转为AST抽象语法树
- return parser.parse(content, {
- sourceType: 'module'
- })
- },
- getDependecies: (ast, filename) => {
- const dependecies = {}
- // 遍历所有的 import 模块,存入dependecies
- traverse(ast, {
- // 类型为 ImportDeclaration 的 AST 节点 (即为import 语句)
- ImportDeclaration({ node }) {
- const dirname = path.dirname(filename)
- // 保存依赖模块路径,之后生成依赖关系图需要用到
- const filepath = './' + path.join(dirname, node.source.value)
- dependecies[node.source.value] = filepath
- }
- })
- return dependecies
- },
- getCode: ast => {
- // AST转换为code
- const { code } = transformFromAst(ast, null, {
- presets: ['@babel/preset-env']
- })
- return code
- }
- }
-
- class Compiler {
- constructor(options) {
- // webpack 配置
- const { entry, output } = options
- // 入口
- this.entry = entry
- // 出口
- this.output = output
- // 模块
- this.modules = []
- }
- // 构建启动
- run() {
- // 解析入口文件
- const info = this.build(this.entry)
- this.modules.push(info)
- this.modules.forEach(({ dependecies }) => {
- // 判断有依赖对象,递归解析所有依赖项
- if (dependecies) {
- for (const dependency in dependecies) {
- this.modules.push(this.build(dependecies[dependency]))
- }
- }
- })
- // 生成依赖关系图
- const dependencyGraph = this.modules.reduce(
- (graph, item) => ({
- ...graph,
- // 使用文件路径作为每个模块的唯一标识符,保存对应模块的依赖对象和文件内容
- [item.filename]: {
- dependecies: item.dependecies,
- code: item.code
- }
- }),
- {}
- )
- }
- build(filename) {
- const { getAst, getDependecies, getCode } = Parser
- const ast = getAst(filename)
- const dependecies = getDependecies(ast, filename)
- const code = getCode(ast)
- return {
- // 文件路径,可以作为每个模块的唯一标识符
- filename,
- // 依赖对象,保存着依赖模块路径
- dependecies,
- // 文件内容
- code
- }
- }
- // 重写 require函数,输出bundle
- generate() {}
- }
-
- new Compiler(options).run()
-
- const fs = require('fs')
- const path = require('path')
- const options = require('./webpack.config')
- const parser = require('@babel/parser')
- const traverse = require('@babel/traverse').default
- const { transformFromAst } = require('@babel/core')
-
- const Parser = {
- getAst: path => {
- // 读取入口文件
- const content = fs.readFileSync(path, 'utf-8')
- // 将文件内容转为AST抽象语法树
- return parser.parse(content, {
- sourceType: 'module'
- })
- },
- getDependecies: (ast, filename) => {
- const dependecies = {}
- // 遍历所有的 import 模块,存入dependecies
- traverse(ast, {
- // 类型为 ImportDeclaration 的 AST 节点 (即为import 语句)
- ImportDeclaration({ node }) {
- const dirname = path.dirname(filename)
- // 保存依赖模块路径,之后生成依赖关系图需要用到
- const filepath = './' + path.join(dirname, node.source.value)
- dependecies[node.source.value] = filepath
- }
- })
- return dependecies
- },
- getCode: ast => {
- // AST转换为code
- const { code } = transformFromAst(ast, null, {
- presets: ['@babel/preset-env']
- })
- return code
- }
- }
-
- class Compiler {
- constructor(options) {
- // webpack 配置
- const { entry, output } = options
- // 入口
- this.entry = entry
- // 出口
- this.output = output
- // 模块
- this.modules = []
- }
- // 构建启动
- run() {
- // 解析入口文件
- const info = this.build(this.entry)
- this.modules.push(info)
- this.modules.forEach(({ dependecies }) => {
- // 判断有依赖对象,递归解析所有依赖项
- if (dependecies) {
- for (const dependency in dependecies) {
- this.modules.push(this.build(dependecies[dependency]))
- }
- }
- })
- // 生成依赖关系图
- const dependencyGraph = this.modules.reduce(
- (graph, item) => ({
- ...graph,
- // 使用文件路径作为每个模块的唯一标识符,保存对应模块的依赖对象和文件内容
- [item.filename]: {
- dependecies: item.dependecies,
- code: item.code
- }
- }),
- {}
- )
- this.generate(dependencyGraph)
- }
- build(filename) {
- const { getAst, getDependecies, getCode } = Parser
- const ast = getAst(filename)
- const dependecies = getDependecies(ast, filename)
- const code = getCode(ast)
- return {
- // 文件路径,可以作为每个模块的唯一标识符
- filename,
- // 依赖对象,保存着依赖模块路径
- dependecies,
- // 文件内容
- code
- }
- }
- // 重写 require函数 (浏览器不能识别commonjs语法),输出bundle
- generate(code) {
- // 输出文件路径
- const filePath = path.join(this.output.path, this.output.filename)
- // 懵逼了吗? 没事,下一节我们捋一捋
- const bundle = `(function(graph){
- function require(module){
- function localRequire(relativePath){
- return require(graph[module].dependecies[relativePath])
- }
- var exports = {};
- (function(require,exports,code){
- eval(code)
- })(localRequire,exports,graph[module].code);
- return exports;
- }
- require('${this.entry}')
- })(${JSON.stringify(code)})`
-
- // 把文件内容写入到文件系统
- fs.writeFileSync(filePath, bundle, 'utf-8')
- }
- }
-
- new Compiler(options).run()
-
我们通过下面的例子来进行讲解,先死亡凝视 30 秒
- ;(function(graph) {
- function require(moduleId) {
- function localRequire(relativePath) {
- return require(graph[moduleId].dependecies[relativePath])
- }
- var exports = {}
- ;(function(require, exports, code) {
- eval(code)
- })(localRequire, exports, graph[moduleId].code)
- return exports
- }
- require('./src/index.js')
- })({
- './src/index.js': {
- dependecies: { './hello.js': './src/hello.js' },
- code: '"use strict";\n\nvar _hello = require("./hello.js");\n\ndocument.write((0, _hello.say)("webpack"));'
- },
- './src/hello.js': {
- dependecies: {},
- code:
- '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports.say = say;\n\nfunction say(name) {\n return "hello ".concat(name);\n}'
- }
- })
-
step 1 : 从入口文件开始执行
- // 定义一个立即执行函数,传入生成的依赖关系图
- ;(function(graph) {
- // 重写require函数
- function require(moduleId) {
- console.log(moduleId) // ./src/index.js
- }
- // 从入口文件开始执行
- require('./src/index.js')
- })({
- './src/index.js': {
- dependecies: { './hello.js': './src/hello.js' },
- code: '"use strict";\n\nvar _hello = require("./hello.js");\n\ndocument.write((0, _hello.say)("webpack"));'
- },
- './src/hello.js': {
- dependecies: {},
- code:
- '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports.say = say;\n\nfunction say(name) {\n return "hello ".concat(name);\n}'
- }
- })
-
step 2 : 使用 eval 执行代码
- // 定义一个立即执行函数,传入生成的依赖关系图
- ;(function(graph) {
- // 重写require函数
- function require(moduleId) {
- ;(function(code) {
- console.log(code) // "use strict";\n\nvar _hello = require("./hello.js");\n\ndocument.write((0, _hello.say)("webpack"));
- eval(code) // Uncaught TypeError: Cannot read property 'code' of undefined
- })(graph[moduleId].code)
- }
- // 从入口文件开始执行
- require('./src/index.js')
- })({
- './src/index.js': {
- dependecies: { './hello.js': './src/hello.js' },
- code: '"use strict";\n\nvar _hello = require("./hello.js");\n\ndocument.write((0, _hello.say)("webpack"));'
- },
- './src/hello.js': {
- dependecies: {},
- code:
- '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports.say = say;\n\nfunction say(name) {\n return "hello ".concat(name);\n}'
- }
- })
-
可以看到,我们在执行"./src/index.js"文件代码的时候报错了,这是因为 index.js 里引用依赖 hello.js,而我们没有对依赖进行处理,接下来我们对依赖引用进行处理。
step 3 : 依赖对象寻址映射,获取 exports 对象
- // 定义一个立即执行函数,传入生成的依赖关系图
- ;(function(graph) {
- // 重写require函数
- function require(moduleId) {
- // 找到对应moduleId的依赖对象,调用require函数,eval执行,拿到exports对象
- function localRequire(relativePath) {
- return require(graph[moduleId].dependecies[relativePath]) // {__esModule: true, say: ƒ say(name)}
- }
- // 定义exports对象
- var exports = {}
- ;(function(require, exports, code) {
- // commonjs语法使用module.exports暴露实现,我们传入的exports对象会捕获依赖对象(hello.js)暴露的实现(exports.say = say)并写入
- eval(code)
- })(localRequire, exports, graph[moduleId].code)
- // 暴露exports对象,即暴露依赖对象对应的实现
- return exports
- }
- // 从入口文件开始执行
- require('./src/index.js')
- })({
- './src/index.js': {
- dependecies: { './hello.js': './src/hello.js' },
- code: '"use strict";\n\nvar _hello = require("./hello.js");\n\ndocument.write((0, _hello.say)("webpack"));'
- },
- './src/hello.js': {
- dependecies: {},
- code:
- '"use strict";\n\nObject.defineProperty(exports, "__esModule", {\n value: true\n});\nexports.say = say;\n\nfunction say(name) {\n return "hello ".concat(name);\n}'
- }
- })
-
这下应该明白了吧 ~ 可以直接复制上面代码到控制台输出哦~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。