webpack.png

1.基础

  1. 1. 初始化package.json
  2. yarn init -y
  3. 安装webpack webpack-cli -D两个webpack依赖包
  4. -D 代表开发依赖包,上线不需要
  5. webpack 可以进行0配置
  6. -> 打包工具 -> 输出后的结果 (js模块)
  7. -> 打包(支持打包模块化)
  8. 手动配置webpack
  9. -默认配置文件的名字 webpack.config.js
  10. 内置webpack-dev-server 静态服务
  11. yarn add webpack-dev-server -D 安装

默认打包

npx webpack 命令

默认执行流程

1.会先找到node_modules下的bin文件夹-> webpack.cmd 文件
2.判断有没有node.exe 有走node.exe 否则走webpack\bin\webpack.js

打包会调用webpack -> 调用webpack-cli

注意点:
webpack 如果要在命令行传参需要加多两个—

  1. // 这样不行
  2. npm run build --config webpack.config.my.js
  3. // 官方说明 如需增加参数应
  4. npm run build -- --config webpack.config.my.js

更改打包配置文件名

  1. // package.json
  2. "scripts": {
  3. "build": "webpack --config webpack.config.my.js"
  4. },

npm scripts

  1. 1. 运行多个脚本
  2. 串行&& 并行& 或||
  3. 2. 日志输出
  4. 3. 脚本传参
  5. 第一第二个元素分别是 node 路径、脚本路径, 从第三个参数开始为脚本输入的参数
  6. process.argv.splice(2) // [ '111', '222', '333' ]
  7. 4. 环境变量 cross-env
  8. 5. 钩子 pre post

钩子执行顺序图
image.png
参考链接: https://juejin.cn/post/6917533974285778957

webpack.config.js

1.base

  1. // webpack 是node写出来的 需要编写node的语法
  2. // 内置node 模块
  3. let path = require('path')
  4. console.log (path.resolve('dist')) // E:\project\webpack\dist
  5. module.exports = {
  6. // 模式 默认有两种production生产 / development开发
  7. mode : "development",
  8. // 入口
  9. entry : './src/index.js',
  10. // 出口
  11. output : {
  12. // 打包后的文件名
  13. filename: "bundle.[hash:8].js",
  14. // 打包后需要放置一个位置, 路径必须是一个绝对路径
  15. // 使用node模块path
  16. path : path.resolve (__dirname, "dist"),
  17. // 路径输出路径域名
  18. publicPath: "https://www.baidu.com"
  19. },
  20. }

2.devServer

  1. module.exports = {
  2. // 静态服务
  3. devServer: {
  4. // 端口号
  5. port : 8880,
  6. // 是否需要进度条
  7. progress : false,
  8. // 基础路径
  9. contentBase: './dist',
  10. // 默认打开浏览器
  11. open : true,
  12. // 压缩
  13. compress : true
  14. },
  15. }

3.plugins

  1. // html插件
  2. let HtmlWebpackPlugin = require('html-webpack-plugin')
  3. // 处理样式抽离一个文件插件 = 1.3.0
  4. let MiniCssExtractPlugin = require('mini-css-extract-plugin')
  5. // 清理dist文件夹
  6. let { CleanWebpackPlugin } = require('clean-webpack-plugin')
  7. // 复制文件插件
  8. let CopyWebpackPlugin = require('copy-webpack-plugin')
  9. // 代码前注释插件
  10. let { BannerPlugin } = require('webpack')
  11. let webpac
  12. module.exports = {
  13. // plugins 插件 数组
  14. plugins : [
  15. new CleanWebpackPlugin(),
  16. new HtmlWebpackPlugin ({
  17. // 模板文件
  18. template: './src/index.html',
  19. // 文件名
  20. filename: "index.html",
  21. // html 压缩
  22. minify : {
  23. // 去掉双引号
  24. removeAttributeQuotes: true,
  25. // 折叠一行
  26. collapseWhitespace : false
  27. },
  28. hash : true
  29. }),
  30. // 抽离文件插件
  31. new MiniCssExtractPlugin({
  32. // 抽离样式的文件名字
  33. filename: 'main.css'
  34. }),
  35. // 复制插件
  36. new CopyWebpackPlugin([
  37. {
  38. from: './src/doc',
  39. to: './doc'
  40. }
  41. ]),
  42. new BannerPlugin('author: 杨明')
  43. ],
  44. }

4.module loader

loader的顺序, 从右向左执行, 由下至上执行

  1. module.exports = {
  2. // 模块
  3. module : {
  4. rules: [
  5. // js语法转换es5 babel
  6. // 相关loader: babel-loader @babel/core 核心模块, @babel-preset-env
  7. {
  8. test: /\.js$/,
  9. use : {
  10. loader : 'babel-loader',
  11. options: {
  12. presets: [
  13. '@babel/preset-env'
  14. ],
  15. // 使用class 更高级的语法 插件
  16. // @babel/plugin-proposal-class-properties
  17. // @babel/plugin-proposal-decorators 装饰器
  18. plugins: [
  19. // '@babel/plugin-proposal-class-properties',
  20. ["@babel/plugin-proposal-decorators", {"legacy": true}],
  21. ["@babel/plugin-proposal-class-properties", {"loose": true}],
  22. // ["@babel/plugin-transform-runtime", {
  23. // "corejs": 2,
  24. // }]
  25. ]
  26. }
  27. },
  28. exclude: /node_modules/,
  29. include: path.resolve(__dirname, './src')
  30. },
  31. {
  32. // css-loader 处理css @import等语法
  33. // style-loader // 插入到head标签中
  34. // loader的特点, 单一原则
  35. // loader的顺序, 从右向左执行, 由下至上执行
  36. test: /\.css$/,
  37. use : [
  38. MiniCssExtractPlugin.loader,
  39. 'css-loader',
  40. // autoprefixer 前缀loader
  41. 'postcss-loader'
  42. ]
  43. },
  44. {
  45. // less-loader< 5.0.0
  46. test: /\.less$/,
  47. use : [
  48. MiniCssExtractPlugin.loader,
  49. 'css-loader',
  50. // 处理less 语法
  51. 'less-loader',
  52. 'postcss-loader'
  53. ]
  54. }
  55. ]
  56. }
  57. }

5.optimization 优化项

  1. // 代码压缩js
  2. let UglifyJsPlugin = require('uglifyjs-webpack-plugin')
  3. // 代码压缩css
  4. let OptimizeCss = require('optimize-css-assets-webpack-plugin')
  5. module.exports = {
  6. optimization: {
  7. // 压缩
  8. minimizer: [
  9. new UglifyJsPlugin({
  10. cache: true,
  11. parallel: true,
  12. sourceMap: true
  13. }),
  14. new OptimizeCss()
  15. ]
  16. },
  17. }

全局变量引入

  1. expose-loader 暴露到window上
  2. ProvidePlugin 给每个人提供一个$
  3. 引入,不打包 ```javascript // import $ from ‘jquery’ // expose-loader 暴露全局的loader, 内联loader // pre执行在前面的loader // normal普通的loader-内联loader // post后置执行的loader

// 在每个模块上注入 $对象 console.log ($) // console.log (window.$)

externals: { jquery: ‘$’ //引入不打包 }

  1. <a name="o5XFQ"></a>
  2. ### 图片打包处理
  3. ```javascript
  4. module.exports = {
  5. module : {
  6. rules: [
  7. {
  8. test: /\.html$/,
  9. use: 'html-withimg-loader'
  10. },
  11. // 文件处理loader
  12. {
  13. test: /\.(jpg|png|gif)$/,
  14. use : {
  15. // url-loader好处当图片小于xxk的时候转换为base64,
  16. // 否则正常file-loader产生真实的图片
  17. loader: 'url-loader',
  18. options: {
  19. limit: 400 * 1024
  20. }
  21. }
  22. },
  23. }
  24. }

2.配置篇

  1. yarn init -y
  2. yarn add webpack webpack-cli -D
  3. 配置webpack.config.js

多页应用

  1. let path = require ('path')
  2. let HtmlWebpackPlugin = require('html-webpack-plugin')
  3. let { CleanWebpackPlugin } = require('clean-webpack-plugin')
  4. module.exports = {
  5. mode : 'development',
  6. // 多入口
  7. entry : {
  8. home : './src/index.js',
  9. other: './src/other.js'
  10. },
  11. output: {
  12. // [name]对应的name
  13. filename: '[name].[hash]js',
  14. path : path.resolve (__dirname, 'dist')
  15. },
  16. plugins: [
  17. new CleanWebpackPlugin(),
  18. new HtmlWebpackPlugin({
  19. template: './src/index.html',
  20. filename: 'home.html',
  21. // 放置代码块
  22. chunks: ['home']
  23. }),
  24. new HtmlWebpackPlugin({
  25. template: './src/index.html',
  26. filename: 'other.html',
  27. chunks: ['other']
  28. })
  29. ]
  30. }

devtool 开发调试

名称 特性 优缺点
source-map 映射源码,会单独创建一个map文件,出错会标识报错的行与列 缺点: 会创建一个大的文件, 优点: 报错信息明细精确
eval-source-map 不会单独创建map文件,但会显示行与列 打包映射到源文件中,
cheap-module-source-map 创建一个map文件映射源码,但不会产生列报错 不便调试
cheap-module-eval-source-map 不会产生文件,集成在打包后的文件中,也不会产生列 最优开发调试方案
  1. module.exports = {
  2. devtool: 'cheap-module-eval-source-map' // 最优方案
  3. }

watch 实时监听打包

  1. module.exports = {
  2. watch: true,
  3. watchOptions: {
  4. // 每秒问1000次 一秒钟访问次数
  5. poll: 1000,
  6. // 防抖, 一直输出代码,500毫秒输出一次
  7. aggregateTimeout: 500,
  8. // 忽略
  9. ignored: /node_module/
  10. }
  11. }

proxy 跨域代理

  1. module.exports ={
  2. devServer: {
  3. open: true,
  4. proxy: {
  5. // 重写的方式,把请求代理到express服务器上
  6. "/api": {
  7. target: '192.168.16.68:7600',
  8. pathRewrite: {
  9. '/api': ''
  10. }
  11. }
  12. }
  13. }
  14. }

resolve [r索] 解析

let { resolve } = require('path')
module.exports = {
  // 默认路径
    resolve: {
    modules: [path.resolve('node_modules')],
    // 入口字段 默认mian
    mainFields: ['style', 'main'],
    // 引入文件没有后缀名时,检测后缀  查找顺序 从左到右
    extensions: ['.js', '.css', '.json', '.vue'],
    alias: {
      bootstrap: 'bootstrap/dist/css/bootstrap.css',
      pathCss: path.resolve(__dirname, 'src/css')
    }
  }
}

定义环境变量

// webpack定义插件
let { DefinePlugin } =  require('webpack')
module.exports = {
    plugins: [
   new DefinePlugin({
      // 如需变为字符串JSON.stringify
      DEV: JSON.stringify('production'),
      FLAG: 'true',
      EXP: '1+1'
    })
  ]
}
// index.js
console.log (DEV) // production
console.log (FLAG) // true
console.log (EXP) // 2

区分不同的环境

// package.json
{
 "scripts": {
    "dev": "webpack-dev-server",
    "build": "webpack --config ./src/build/webpack.prod.config.js",
    "build:dev": "webpack --config ./src/build/webpack.dev.config.js"
  }
}
// webpack.dev.config 
let { merge } = require('webpack-merge')
let base = require('./../../webpack.base')
module.exports = merge(base, {
  mode: 'development',
  // .....
})
//webpack.prod.config
let { merge } = require('webpack-merge')
let base = require('./../../webpack.base')
module.exports = merge(base, {
  mode: 'production',
  devtool: 'none',
  optimization: {
    minimizer: []
  },
  plugins: []
})

3.webpack优化

noParse-module配置

不需要解析的包, 假如我们知道包没有别的依赖,可以noParse

module.exports = {
module: {
    // 不需要解析的包, 假如我们知道包没有别的依赖,可以noParse
    noParse: /jquery/,
    rules: [
      // ...
    ]
  }
}

IgnorePlugin-插件

// webpack内置忽略引入插件
let { IgnorePlugin } = require('webpack')
module.exports = {
  plugins: [
    // 忽略加载moment 下的locale所有文件
    new IgnorePlugin(/\.\/locale/, /moment/)
  ]        
}
// index.js

import moment from 'moment'
// 如果忽略后,设置不生效, 但我们可以手动引入
moment.locale('zh-cn')
import 'moment/locale/zh-cn'

let r = moment().endOf('day').fromNow()
console.log (r) // xxx小时

DllPlugin-插件-动态列js库

1.先打包固定的js库,缓存下来 DllPlugin
2.webpack.config直接DllReferencePlugin()

// path: build/webpack.config.react.js

let { resolve } = require('path')
let { DllPlugin } = require('webpack')

module.exports = {
  mode: "development",
  entry: {
    react: [
      'react',
      'react-dom'
    ]
  },
  output: {
    // 动态打包的名字
    filename: '_dll_[name].js',
    // 存放位置
    path: resolve(__dirname, '../dist'),
    // 导出的包名-对应关联
    library: '_dll_[name]',
    // 使用语法
    libraryTarget: "var"
  },
  plugins: [
    // name 需要 === library
    new DllPlugin({
      name:  '_dll_[name]',
      // manifest 用户清单
      path: resolve(__dirname, '../dist', 'manifest.json')
    })
  ]
}
// path: webpack.config.js
let { DllReferencePlugin } = require('webpack')
module.exports = {
    plugins: [
   // 查找依赖时先去查找manifest.json下的dll任务清单
    new DllReferencePlugin({
      manifest: resolve(__dirname, 'dist', 'manifest.json')
    })
  ]
}

Happypack 多线程打包-插件

采用多线程打包

  1. loader使用happypack
  2. plugins new Happypack

    // webpack.config.js
    let Happypack = require('happypack')
    // yarn add happypack 安装
    module.exports = {
      module   : {
       rules  : [
         {
           test: /\.js$/,
           use : 'Happypack/loader?id=js'
         },
         {
           test: /\.css$/,
           use : 'Happypack/loader?id=css'
         }
       ]
    },
    plugins: [
           new Happypack ({
       id : 'js',
       use: [
         {
           loader : 'babel-loader',
           options: {
             presets: [
               '@babel/preset-env',
               '@babel/preset-react'
             ]
           }
         }
       ]
     }),
     new Happypack ({
       id : 'css',
       use: ['style-loader', "css-loader"]
     })
    ]
    }
    

    webpack自带的优化

    在生产模式下会自动做两个事情

  3. tree-shaking 只有es6 import/export语法才生效

  4. scope hosting ```javascript // index.js

// import 语法webpack在生产环境下 会自动去掉没有引用的代码 // tree-shaking优化 只有Es6语法才会支持tree-shaking import calc from ‘./test’ // calc: { sum, minus } console.log (calc.sum (1, 2))

// scope hosting 作用域提升 let a = 1 let b = 2 let c = 3 let d = a + b + c console.log (d, ‘ ————————————-‘) const fun1 = () => { return d + a } console.log (fun1(), ‘222222222’)

// 打包后的代码 // console.log (6, “ ————————————-“); // console.log (7, “222222222”)

<a name="U4Nqo"></a>
### splitChunks 把公共代码抽离出来
1.公共抽离 common<br />2.第三方抽离 vendor
```javascript
// webpack.config.js

module.exports = {
    // 优化项
  optimization: {
    // 分割代码块
    splitChunks: {
      // 缓存组
      cacheGroups: {
        // 公共的模块
        common: {
          chunks: "initial",
          minSize: 1,
          // 最少使用次数
          minChunks: 2
        },
        // 第三方模块抽离
        vendor: {
          // 权重
          priority: 1,
          // 把node_modules下的依赖包抽离出来
          test: /node_modules/,
          chunks: "initial",
          minSize: 1,
          // 最少使用次数
          minChunks: 2
        }
      }
    }
  }
}

懒加载 import(../xx)

依赖webpack import() 动态加载

// index.js

let btn = document.createElement ('button')
btn.innerHTML = '添加按钮'
btn.addEventListener ('click', () => {
  // es6 草案中的语法, jsonp实现动态加载文件
  import('./source').then (data => {
    // Module {default: "杨明炜", __esModule: true, Symbol(Symbol.toStringTag): "Module"}
      console.log (data) 
  })
})

document.body.appendChild (btn)

热更新

HotModuleReplacementPlugin

// webpack.config.js

let webpack = require('webpack')
module.exports = {
    plugins: [
      new webpack.HotModuleReplacementPlugin()
  ]
}

4.tapable

sync 同步

SyncHook 调用call() 同步顺序执行
SyncBailHook 调用call() 如果返回不是undefined 中止执行
SyncWaterfallHook 调用call() 顺序执行,上一个结果返回给下一个
SyncLoopHook 调用call() 顺序执行,任务如果不是返回undefined 继续执行当前

SyncHook

// 手写SyncHook方法
// 调用call() 同步顺序执行
class SyncHook {
  constructor() {
    // 增加任务队列
    this.tasks = []
  }

  tap(name, task) {
    this.tasks.push (task)
  }

  // 队列同步执行
  call(...args) {
    this.tasks.forEach (task => task (...args))
  }
}

let hook = new SyncHook (['name'])
// 添加任务
hook.tap ('杨明', (name) => {
  console.log (name, '杨明')
})
hook.tap ('明炜', (name) => {
  console.log (name, '明炜')
})
// 调用执行方法
hook.call ('你好啊')
// 你好啊 杨明
// 你好啊 明炜

SyncBailHook

// 调用call() 顺序执行如果返回undefined 才会继续往下执行
class SyncBallHook {
  constructor() {
    // 增加任务队列
    this.tasks = []
  }

  tap(name, task) {
    this.tasks.push (task)
  }

  // 执行task 如果返回不是undefined 中止执行
  call(...args) {
    let ret;
    let index = 0
    do {
      ret = this.tasks[index++] (...args)
    } while (ret === undefined && index < this.tasks.length)
  }
}

let hook = new SyncBallHook (['name'])
// 添加任务
hook.tap ('杨明', (name) => {
  console.log (name, '杨明')
})
hook.tap ('明炜', (name) => {
  console.log (name, '明炜')
  return '学不动了'
})
hook.tap ('杨明炜', (name) => {
  console.log (name, '杨明炜')
})
// 调用执行方法
hook.call ('你好啊')
// 你好啊 杨明
// 你好啊 明炜

SyncWaterfallHook

// 调用call() 顺序执行,上一个结果返回给下一个
class SyncWaterfallHook {
  constructor() {
    // 增加任务队列
    this.tasks = []
  }

  tap(name, task) {
    this.tasks.push (task)
  }

  call(...args) {
    let [first, ...other] = this.tasks
    let result = first (...args)
    other.reduce ((prev, next) => {
      return next (prev, ...args)
    }, result)
  }
}

let hook = new SyncWaterfallHook (['name'])
// 添加任务
hook.tap ('杨明', (name, data) => {
  console.log (name, '杨明', data)
  return '杨明加油'
})
hook.tap ('明炜', (name, data) => {
  console.log (name, '明炜', data)
  return '明炜加油'
})
hook.tap ('杨明炜', (name, data) => {
  console.log (name, '杨明炜', data)
  return '学不动啦'
})
// 调用执行方法
hook.call ('你好啊')
// 你好啊 杨明 undefined
// 杨明加油 明炜 你好啊
// 明炜加油 杨明炜 你好啊

SyncLoopHook

// 调用call() 顺序执行,任务如果不是返回undefined 继续执行当前
class SyncLoopHook {
  constructor() {
    // 增加任务队列
    this.tasks = []
  }

  tap(name, task) {
    this.tasks.push (task)
  }

  call(...args) {

    this.tasks.forEach (task => {
      let result
      do {
        result = task (...args)
      } while (result !== undefined)

    })
  }
}

let hook = new SyncLoopHook (['name'])
let learnNumber = 0
// 添加任务
hook.tap ('杨明', (name) => {
  console.log (name, '杨明')
  return ++learnNumber === 3 ? undefined : '看不懂就多看几遍'
})
hook.tap ('明炜', (name) => {
  console.log (name, '明炜')
})
hook.tap ('杨明炜', (name) => {
  console.log (name, '杨明炜')
})
// 调用执行方法
hook.call ('你好啊')
// 你好啊 杨明
// 你好啊 杨明
// 你好啊 杨明
// 你好啊 明炜 undefined
// 你好啊 杨明炜 undefined

异步

AsyncParallHook 异步 并行执行
AsyncSeriesHook 异步 串行执行 有回调
AsyncSeriesWaterfallHook 异步串行下一个得到上一个返回值,返回非null, 跳出异步执行

AsyncParallelHook

// 异步的钩子 (串行/并行) 需要等待所有并发的异步时间执行成功后才会执行回调方法
// tapable库中有三种注册方法
// tap注册, tapAsync(cb)异步注册 tapPromise(promise)
class AsyncParallelHook {
  constructor() {
    // 增加任务队列
    this.tasks = []
  }

  tapPromise(name, task) {
    this.tasks.push (task)
  }

  // 队列同步执行
  promise(...args) {
    let tasks = this.tasks.map(task => task (...args))
    return Promise.all(tasks)
  }
}

let hook = new AsyncParallelHook (['name'])
// 添加任务
hook.tapPromise ('node', name => {
  return new Promise ((resolve, reject) => {
    setTimeout (_ => {
      console.log (name, 'learn node')
      resolve ('node,学习完了')
    }, 1000)
  })
})
hook.tapPromise ('webpack', name => {
  return new Promise ((resolve, reject) => {
    setTimeout (_ => {
      console.log (name, 'learn webpack')
      resolve ('webpack,学习完了')
    }, 4000)
  })
})
// 调用执行方法
hook.promise('杨明').then( data => {
  console.log ('end', data)
})
// 杨明 learn node
// 杨明 learn webpack
// end [ 'node,学习完了', 'webpack,学习完了' ]

AsyncSeriesHook

// 串行钩子
class AsyncSeriesHook {
  constructor() {
    // 增加任务队列
    this.tasks = []
  }
  tapAsync(name, task) {
    this.tasks.push (task)
  }
  // 异步串行
  callAsync(...args) {
    // 获取参数的最后一个回调函数
    let finalCallback = args.pop()
    let index = 0
    let next = _ => {
      // 如果走完任务 执行回调
      if (index === this.tasks.length) return finalCallback()
      let task = this.tasks[index++]
      task(...args, next)
    }
    next()
  },
  // 改写promise版本
  promise(...args) {
    let [first, ...other] = this.tasks
    return other.reduce ((promise, next) => {
      return promise.then (_ => next ())
    }, first (...args))
  }
}

let hook = new AsyncSeriesHook (['name'])
// 添加任务
hook.tapAsync ('node', (name, cb) => {
    setTimeout (_ => {
      console.log (name, 'learn node')
      cb ('node,学习完了')
    }, 3000)
})
hook.tapAsync ('webpack',  (name, cb) => {
    setTimeout (_ => {
      console.log (name, 'learn webpack')
      cb ('webpack,学习完了')
    }, 1000)
})
// 调用执行方法
hook.callAsync('杨明', data => {
  console.log ('end', data)
})
// 杨明 learn node
// 杨明 learn webpack
// end undefined

AsyncSeriesWaterfallHook

// 串行 瀑布钩子 把上一个的返回值传递给下一个, 最终执行回调
class AsyncSeriesWaterfallHook {
  constructor() {
    // 增加任务队列
    this.tasks = []
  }
  tapAsync(name, task) {
    this.tasks.push (task)
  }
  callAsync(...args) {
    let finalCallback = args.pop ()
    let index = 0
    let next = (err, data) => {
      // 如果是第一个把arg参数放进去, 否则给回调返回参数
      if (index === this.tasks.length || (err !== null && index !== 0)) return finalCallback (data)
      let task = this.tasks[index]
      if (index === 0) {
        task (...args, next)
      } else {
        task (data, next)
      }
      index++
    }
    next ()
  }
}

let hook = new AsyncSeriesWaterfallHook (['name'])
// 添加任务
hook.tapAsync ('node', (name, cb) => {
  setTimeout (_ => {
    console.log (name, 'learn node')
    cb ('error', 'node,学习完了')
  }, 3000)
})
hook.tapAsync ('webpack', (name, cb) => {
  setTimeout (_ => {
    console.log (name, 'learn webpack')
    cb (null, 'webpack,学习完了')
  }, 1000)
})
// 调用执行方法
hook.callAsync ('杨明', data => {
  console.log ('end', data)
})
// 杨明 learn node
// end node,学习完了

5.手写简单webpack

1.npm link
需要注意的的是, packageName 是取自包的 package.jsonname 字段,不是文件夹名称。

使用node编写一个简易版本webpack打包

  1. 获取webpack.config.js 配置 config
  2. 编写Compiler类函数 new一个Compiler实例把config传入构造函数上缓存取来
  3. 调用compiler的run方法
  4. run方法分别执行 构建模块/发射文件两个大方法
  5. 构建模块

1.使用node的fs模块 读取文本内容
2.把绝对路径转换为相对路径缓存在modules以路径为key, 编译好的内容为value缓存起来
3.编译使用 babylon及traverse编译重写方法名为webpack_require
4.然后递归查找构建模块下有没有require引用

  1. 发射文件使用ejs模板文件生成文件,并写入到配置到output下
  2. loader在读取文件的时候做loader编译

项目代码: github/webpack