zoukankan      html  css  js  c++  java
  • webpack02

    学了一段时间的webpack,总觉得插件太多,麻烦。在学习过程中得知了vue-cli脚手架工具。为啥我学vue的时候没有听说过这个。。

    好了,来说一下vue-cli吧。它是一个专门为单页面应用快速搭建繁杂的脚手架,它可以轻松的创建新的应用程序而且可用于自动生成vue和webpack的项目模板。看了webpack四五天之后

    就去试了一下这个脚手架,很好用的。之前webpack就想偷偷懒了。直接找了一下别人学习webpack的笔记。嘿嘿。虽然脚手架很方便,但是现在企业都需要知其然,知其所以然。熟悉

    webpack源代码还是非常有帮助的。react额话,也有create-react-app这样的脚手架。以后学react的时候在试一试吧。不厚道的我直接拿别人的笔记过来了。

    转载

    https://github.com/TigerHee/shareJS/blob/master/webpack4.md

    安装前先npm初始化
    本地服务
    复制html
    处理css
    处理less
    抽离css文件,通过link引入
    压缩css和js
    给css加上兼容浏览器的前缀
    es6 转 es5
    es 7的语法
    全局变量引入
    webpack图片打包
    当图片小于多少,用base64
    打包文件分类
    希望输出的时候,给这些cssimg加上前缀,传到服务器也能访问
    如果只希望处理图片
    打包多页应用
    配置source-map
    watch 改完代表重新打包实体
    webpack的其他三个小插件
    webpack 跨域
    如果后端给的请求没有API 「跨域」
    前端只想单纯mock数据 「跨域」
    有服务端,不用代理, 服务端启动webpack 「跨域」
    webpack解析resolve
    但是每次引入都很长,如何优雅引入
    省略扩展名
    定义环境变量
    区分两个不同的环境
    webpack 优化
    优化:当某些包是独立的个体没有依赖
    优化:规则匹配设置范围
    优化:忽略依赖中不必要的语言包
    动态链接库
    多线程打包happypack
    webpack 自带的优化
    抽取公共代码
    懒加载(延迟加载)
    热更新(当页面改变只更新改变的部分,不重新打包)
    tapable介绍 - SyncHook
    tapable介绍 - SyncBailHook
    tapable介绍 - SyncWaterfallHook
    tapable介绍 - SyncLoopHook
    AsyncParallelHook 与 AsyncParallelBailHook
    AsyncParallelHook
    AsyncParallelBailHook
    异步串行 —— AsyncSeriesHook
    异步串行 —— AsyncSeriesWaterfallHook
    手写webpack
    webpack分析及处理
    创建依赖关系
    ast递归解析
    生成打包工具
    增加loader
    增加plugins
    loader
    配置多个loader
    babel-loader实现
    banner-loader实现(自创)
    实现file-loader和url-loader
    less-loader和css-loader
    css-loader
    webpack 中的插件
    文件列表插件
    内联的webpack插件
    打包后自动发布

    安装前先npm初始化
    npm init -y
    npm i webpack webpack-cli -D
    let path = require('path') // 相对路径变绝对路径

    module.exports = {
    mode: 'production', // 模式 默认 production development
    entry: './src/index', // 入口
    output: {
    filename: 'bundle.[hash:8].js', // hash: 8只显示8位
    path: path.resolve(__dirname, 'dist'),
    publicPath: '' // // 给所有打包文件引入时加前缀,包括css,js,img,如果只想处理图片可以单独在url-loader配置中加publicPath
    }
    }
    本地服务
    npm i webpack-dev-server -D

    devServer: {
    port: 3000,
    progress: true // 滚动条
    contentBase: './build' // 起服务的地址
    open: true // 自动打开浏览器
    compress: true // gzip压缩
    }
    复制html
    npm i html-webpack-plugin -D

    let HtmlWebpackPlugin = require('html-webpack-plugin')
    plugins: [ // 放着所有webpack插件
    new HtmlWebpackPlugin({ // 用于使用模板打包时生成index.html文件,并且在run dev时会将模板文件也打包到内存中
    template: './index.html', // 模板文件
    filename: 'index.html', // 打包后生成文件
    hash: true, // 添加hash值解决缓存问题
    minify: { // 对打包的html模板进行压缩
    removeAttributeQuotes: true, // 删除属性双引号
    collapseWhitespace: true // 折叠空行变成一行
    }
    })
    ]

    html-webpack-plugin#options

    处理css
    npm i css-loader style-loader -D

    // css-loader 作用:用来解析@import这种语法
    // style-loader 作用:把 css 插入到head标签中
    // loader的执行顺序: 默认是从右向左(从下向上)
    module: { // 模块
    rules: [ // 规则
    // style-loader 把css插入head标签中
    // loader 功能单一
    // 多个loader 需要 []
    // 顺便默认从右到左
    // 也可以写成对象方式
    {
    test: /.css$/, // css 处理
    // use: 'css-loader'
    // use: ['style-loader', 'css-loader'],
    use: [
    // {
    // loader: 'style-loader',
    // options: {
    // insertAt: 'top' // 将css标签插入最顶头 这样可以自定义style不被覆盖
    // }
    // },
    MiniCssExtractPlugin.loader,
    'css-loader', // css-loader 用来解析@import这种语法,
    'postcss-loader'
    ]
    }
    ]
    }
    处理less
    npm i less-loader

    {
    test: /.less$/, // less 处理
    // use: 'css-loader'
    // use: ['style-loader', 'css-loader'],
    use: [
    // {
    // loader: 'style-loader',
    // options: {
    // insertAt: 'top' // 将css标签插入最顶头 这样可以自定义style不被覆盖
    // }
    // },
    MiniCssExtractPlugin.loader, // 这样相当于抽离成一个css文件, 如果希望抽离成分别不同的css, 需要再引入MiniCssExtractPlugin,再配置
    'css-loader', // css-loader 用来解析@import这种语法
    'postcss-loader',
    'less-loader' // less-loader less -> css
    // sass node-sass sass-loader
    // stylus stylus-loader
    ]
    }
    less-loader

    抽离css文件,通过link引入
    yarn add mini-css-extract-plugin -D

    mini-css-extract-plugin

    let MiniCssExtractPlugin = require('mini-css-extract-plugin')

    // 压缩css

    plugins: [
    new MiniCssExtractPlugin({
    filename: 'css/main.css'
    })
    ]

    {
    test: /.css$/, // css 处理
    // use: 'css-loader'
    // use: ['style-loader', 'css-loader'],
    use: [
    // {
    // loader: 'style-loader',
    // options: {
    // insertAt: 'top' // 将css标签插入最顶头 这样可以自定义style不被覆盖
    // }
    // },
    // 此时不需要style-loader
    MiniCssExtractPlugin.loader, // 抽离
    'css-loader', // css-loader 用来解析@import这种语法,
    'postcss-loader'
    ]
    }

    抽离css插件文件时可使用optimize-css-assets-webpack-plugin优化压缩css以及js文件

    压缩css和js
    // 用了mini-css-extract-plugin抽离css为link需使用optimize-css-assets-webpack-plugin进行压缩css,使用此方法压缩了css需要uglifyjs-webpack-plugin压缩js
    const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin")
    const UglifyJsPlugin = require("uglifyjs-webpack-plugin")

    module.exports = {
    optimization: { // 优化项
    minimizer: [
    new UglifyJsPlugin({ // 优化js
    cache: true, // 是否缓存
    parallel: true, // 是否并发打包
    // sourceMap: true // 源码映射 set to true if you want JS source maps
    }),
    new OptimizeCSSAssetsPlugin({}) // css 的优化
    ]
    },
    mode: 'production',
    entry: '',
    output: {},
    }

    给css加上兼容浏览器的前缀
    yarn add postcss-loader autoprefixer -D

    // css
    {
    test: /.css$/, // css 处理
    // use: 'css-loader'
    // use: ['style-loader', 'css-loader'],
    use: [
    // {
    // loader: 'style-loader',
    // options: {
    // insertAt: 'top' // 将css标签插入最顶头 这样可以自定义style不被覆盖
    // }
    // },
    MiniCssExtractPlugin.loader,
    'css-loader', // css-loader 用来解析@import这种语法,
    'postcss-loader'
    ]
    }
    // less
    {
    test: /.less$/, // less 处理
    // use: 'css-loader'
    // use: ['style-loader', 'css-loader'],
    use: [
    // {
    // loader: 'style-loader',
    // options: {
    // insertAt: 'top' // 将css标签插入最顶头 这样可以自定义style不被覆盖
    // }
    // },
    MiniCssExtractPlugin.loader, // 这样相当于抽离成一个css文件, 如果希望抽离成分别不同的css, 需要再引入MiniCssExtractPlugin,再配置
    'css-loader', // css-loader 用来解析@import这种语法
    'postcss-loader',
    'less-loader' // less-loader less -> css
    // sass node-sass sass-loader
    // stylus stylus-loader
    ]
    },
    postcss 需要配置文档 postcss.config1.js

    postcss-loader

    module.exports = {
    plugins: [
    require('autoprefixer')
    ]
    }
    es6 转 es5
    npm i babel-loader @babel/core @babel/preset-env -D

    module.exports = {
    module: {
    rules: [
    {
    test: /.js$/,
    use: {
    loader: 'babel-loader',
    options: {
    presets: [ //预设
    '@babel/preset-env'
    ],
    plugins:[
    // 转es7的语法
    ["@babel/plugin-proposal-decorators", { "legacy": true }],
    ["@babel/plugin-proposal-class-properties", { "loose" : true }]
    ]
    }
    },
    exclude: /node_modules/
    }
    ]
    }
    }

    转es7的语法
    // 转class
    npm i @babel/plugin-proposal-class-properties -D

    // 转装饰器
    npm i @babel/plugin-proposal-decorators -D
    配置如上

    其他不兼容的高级语法
    使用 @babel/polyfill
    语法检查 eslint
    npm i eslint eslint-loader -S

    根目录添加 .eslintrc.json 配置文件

    module.exports = {
    module: {
    rules: [
    {
    test: /.js$/,
    use: {
    loader: 'eslint-loader',
    options: {
    enforce: 'pre' // previous优先执行 post-普通loader之后执行
    }
    }
    },
    {
    test: /.js$/, // mormal 普通的loader
    use: {
    loader: 'babel-loader',
    options: {
    presets: [ //预设
    '@babel/preset-env'
    ]
    }
    },
    exclude: /node_modules/
    }
    ]
    }
    }

    全局变量引入
    jquery的引入

    npm i jquery -S
    let webpack = require('webpack')

    new webpack.ProvidePlugin({
    $: 'jquery'
    })
    其他情况

    暴露全局
    npm i expose-loader -D 暴露全局的loader

    法1:
    可以在js中 import $ from 'expose-loader?$!jquery' // 全局暴露jquery为$符号

    可以调用window.$

    法2:
    也可在webpack.config.js 中配置 rules

    module.exports = {
    module: {
    rules: [
    {
    test: require.resolve('jquery'),
    use: 'expose-loader?$'
    }
    ]
    }
    }

    以后在.js文件中引入

    import $ from 'jquery'
    法3. 如何在每个模块中注入:
    let webpack = require('webpack')

    module.exports = {
    plugins: [
    new webpack.ProvidePlugin({
    $: 'jquery'
    })
    ]
    }

    之后代码内直接使用 $
    法4:
    在index.html中通过script标签引入jquery, 但是在js中,用import会重新打包jquery,如何避免

    从输出的bundle 中排除依赖

    module.exports = {
    externals: { // 告知webpack是外部引入的,不需要打包
    jquery: 'jQuery'
    }
    }

    此时在index.js上

    import $ from 'jquery'

    console.log($)
    webpack图片打包
    js中创建
    css中引入

    yarn add file-loader -D

    适合一二情况

    module.export={
    module: {
    rules: [
    {
    test: /.(png|jpg|gif)$/,
    use: 'file-loader'
    }
    ]
    }
    }

    默认会内部生成一张图片到build,生成图片的路径返回回来

    第一种情况: 图片地址要import引入,直接写图片的地址,会默认为字符串

    import logo from './logo.png'

    let image = new Image()
    image.src = logo
    document.body.appendChild(image)
    第二种情况: css-loader会将css里面的图片转为require的格式

    div {
    background: url("./logo.png");
    }
    第三种情况: 解析html中的image

    yarn add html-withimg-loader -D

    {
    test: /.html$/,
    use: 'html-withimg-loader'
    }
    当图片小于多少,用base64
    yarn add url-loader -D

    如果过大,才用file-loader

    {
    test: /.(png|jpg|gif)$/,
    // 当图片小于多少,用base64,否则用file-loader产生真实的图片
    use: {
    loader: 'url-loader',
    options: {
    limit: 200 * 1024, // 小于200k变成base64
    // outputPath: '/img/', // 打包后输出地址
    // publicPath: '' // 给资源加上域名路径
    }
    }
    }
    打包文件分类
    1.图片:

    {
    test: /.(png|jpg|gif)$/,
    // 当图片小于多少,用base64,否则用file-loader产生真实的图片
    use: {
    loader: 'url-loader',
    options: {
    limit: 1, // 200k 200 * 1024
    outputPath: 'img/' // 打包后输出地址 在dist/img
    }
    }
    },
    2.css:

    plugins: [
    new MiniCssExtractPlugin({
    filename: 'css/main.css'
    }),
    ]
    希望输出的时候,给这些cssimg加上前缀,传到服务器也能访问
    output: {
    filename: 'bundle.[hash:8].js', // hash: 8只显示8位
    path: path.resolve(__dirname, 'dist'),
    publicPath: 'http://www.mayufo.cn' // 给静态资源统一加
    },
    如果只希望处理图片
    {
    test: /.(png|jpg|gif)$/,
    // 当图片小于多少,用base64,否则用file-loader产生真实的图片
    use: {
    loader: 'url-loader',
    options: {
    limit: 1, // 200k 200 * 1024
    outputPath: '/img/', // 打包后输出地址
    publicPath: 'http://www.mayufo.cn'
    }
    }
    }
    打包多页应用
    // 多入口
    let path = require('path')
    let HtmlWebpackPlugin = require('html-webpack-plugin')

    module.exports = {
    mode: 'development',
    entry: {
    home: './src/index.js',
    other: './src/other.js'
    },
    output: {
    filename: "[name].js",
    path: path.resolve(__dirname, 'dist2')
    },
    plugins: [
    new HtmlWebpackPlugin({
    template: './index.html',
    filename: 'home.html',
    chunks: ['home']
    }),
    new HtmlWebpackPlugin({
    template: './index.html',
    filename: 'other.html',
    chunks: ['other', 'home'] // other.html 里面有 other.js & home.js
    }),
    ]
    }

    配置source-map
    yarn add @babel/core @babel/preset-env babel-loader webpack-dev-server -D

    module.exports = {
    devtool: 'source-map' // 增加映射文件调试源代码
    }
    源码映射 会标识错误的代码 打包后生成独立的文件 大而全 「source-map」
    不会陈胜单独的文件 但是可以显示行和列 「eval-source-map」
    不会产生列有行,产生单独的映射文件 「cheap-module-source-map」
    不会产生文件 集成在打包后的文件中 不会产生列有行 「cheap-module-eval-source-map」
    watch 改完代表重新打包实体
    module.exports = {
    watch: true,
    watchOptions: {
    poll: 1000, // 每秒监听1000次
    aggregateTimeout: 300, // 防抖,当第一个文件更改,会在重新构建前增加延迟
    ignored: /node_modules/ // 对于某些系统,监听大量文件系统会导致大量的 CPU 或内存占用。这个选项可以排除一些巨大的文件夹,
    },
    }
    webpack的其他三个小插件
    cleanWebpackPlugin
    每次打包之前删掉dist目录 yarn add clean-webpack-plugin -D

    clean-webpack-plugin

    const CleanWebpackPlugin = require('clean-webpack-plugin');

    module.exports = {
    output: {
    path: path.resolve(process.cwd(), 'dist'),
    },
    plugins: [
    new CleanWebpackPlugin('./dist')
    ]
    }
    copyWebpackPlugin
    一些静态资源也希望拷贝的dist中

    yarn add copy-webpack-plugin -D

    const CopyWebpackPlugin = require('copy-webpack-plugin')

    module.exports = {
    plugins: [
    new CopyWebpackPlugin([
    {from: 'doc', to: './dist'}
    ])
    ]
    }
    bannerPlugin内置模块
    版权声明

    const webpack = require('webpack');

    new webpack.BannerPlugin('hello world')
    // or
    new webpack.BannerPlugin({ banner: 'hello world'})

    webpack 跨域
    设置一个服务,由于webpack-dev-server内含express

    express

    server.js

    // express

    let express = require('express')

    let app = express();

    app.get('/api/user', (res) => {
    res.json({name: 'mayufo'})
    })

    app.listen(3000) // 服务端口在3000
    写完后记得node server.js

    访问 http://localhost:3000/api/user 可见内容

    index.js

    // 发送一个请求
    let xhr = new XMLHttpRequest();

    // 默认访问 http://localhost:8080 webpack-dev-server 的服务 再转发给3000
    xhr.open('GET', '/api/user', true);

    xhr.onload = function () {
    console.log(xhr.response)
    }

    xhr.send();

    webpack.config.js

    module.exports = {
    devServer: {
    proxy: {
    '/api': 'http://localhost:3000'
    }
    },
    }
    1.如果后端给的请求没有API 「跨域」
    // express

    let express = require('express')

    let app = express();

    app.get('/user', (res) => {
    res.json({name: 'mayufo'})
    })

    app.listen(3000) // 服务端口在3000
    请求已api开头, 转发的时候再删掉api

    devServer: {
    proxy: {
    '/api': {
    target: 'http://localhost:3000',
    pathRewrite: {'^/api': ''}
    }
    }
    }
    2.前端只想单纯mock数据 「跨域」
    devServer: {
    // proxy: {
    // '/api': 'http://localhost:3000' // 配置一个代理
    // }
    // proxy: { // 重写方式 把请求代理到express 上
    // '/api': {
    // target: 'http://localhost:3000',
    // pathRewrite: {'^/api': ''}
    // }
    // }
    before: function (app) { // 勾子
    app.get('/api/user', (req, res) => {
    res.json({name: 'tigerHee'})
    })
    }
    },
    3.有服务端,不用代理, 服务端启动webpack 「跨域」
    server.js中启动webpack

    yarn add webpack-dev-middleware -D

    server.js

    // express

    let express = require('express')
    let webpack = require('webpack')
    let app = express();

    // 中间件
    let middle = require('webpack-dev-middleware')

    let config = require('./webpack.config')

    let compiler = webpack(config)

    app.use(middle(compiler))

    app.get('/user', (req, res) => {
    res.json({name: 'mayufo'})
    })

    app.listen(3000)

    webpack解析resolve
    以bootstrap为例

    npm install bootstrap -D
    index.js

    import 'bootstrap/dist/css/bootstrap.css'
    报错

    ERROR in ./node_modules/bootstrap/dist/css/bootstrap.css 7:0
    Module parse failed: Unexpected token (7:0)
    You may need an appropriate loader to handle this file type.
    | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
    | */

    :root {
    | --blue: #007bff;
    | --indigo: #6610f2;
    @ ./src/index.js 22:0-42
    @ multi (webpack)-dev-server/client?http://localhost:8081 ./src/index.js

    这是因为bootstrap 4.0的css引入了新的特性,CSS Variables

    安装 npm install postcss-custom-properties --save-dev

    配置webpack.config.js

    {
    test: /.css$/,
    use: ['style-loader', 'css-loader', {
    loader: 'postcss-loader',
    options: {
    plugins: (loader) => [
    require("postcss-custom-properties")
    ]
    }
    }]
    }
    但是每次引入都很长,如何优雅引入
    resolve: {
    // 在当前目录查找
    modules: [path.resolve('node_modules')],
    alias: {
    'bootstrapCss': 'bootstrap/dist/css/bootstrap.css'
    }
    },
    import 'bootstrapCss' // 在node_modules查找
    省略扩展名
    extensions:

    resolve: {
    // 在当前目录查找
    modules: [path.resolve('node_modules')],
    // alias: {
    // 'bootstrapCss': 'bootstrap/dist/css/bootstrap.css'
    // },
    mainFields: ['style', 'main'], // 先用bootstrap中在package中的style,没有在用main
    // mainFiles: [] // 入口文件的名字 默认index
    extensions: ['.js', '.css', '.json'] // 当没有拓展命的时候,先默认js、次之css、再次之json
    },
    定义环境变量
    DefinePlugin 允许创建一个在编译时可以配置的全局常量。这可能会对开发模式和生产模式的构建允许不同的行为非常有用。

    let url = ''
    if (DEV === 'dev') {
    // 开发环境
    url = 'http://localhost:3000'
    } else {
    // 生成环境
    url = 'http://www.mayufo.cn'
    }
    webpack.config.js

    new webpack.DefinePlugin({
    // DEV: '"production"',
    DEV: JSON.stringify('production'),
    FLAG: 'true', // 布尔
    EXPRESSION: '1 + 1' // 字符串 如果希望是字符串 JSON.stringify('1 + 1')
    })
    区分两个不同的环境
    分别配置不同的环境

    webpack.base4.js 基础配置
    webpack.dev4.js 开发环境
    webpack.prod4.js 生产环境
    yarn add webpack-merge -D

    npm run build -- -- config webpack.dev4.js npm run build -- -- config webpack.build.js

    官方文档

    webpack.base4.js

    let path = require('path')
    let HtmlWebpackPlugin = require('html-webpack-plugin')
    let CleanWebpackPlugin = require('clean-webpack-plugin')

    module.exports = {
    entry: {
    home: './src/index.js'
    },
    output: {
    filename: "[name].js",
    path: path.resolve(process.cwd(), 'dist3')
    },
    module: {
    rules: [
    {
    test: /.js$/,
    use: {
    loader: 'babel-loader',
    options: {
    presets: [
    '@babel/preset-env'
    ]
    }
    }
    },
    {
    test: /.css$/,
    use: ['style-loader', 'css-loader', {
    loader: 'postcss-loader',
    options: {
    plugins: (loader) => [
    require("postcss-custom-properties")
    ]
    }
    }]
    }
    ]
    },
    plugins: [
    new HtmlWebpackPlugin({
    template: './src/index.html',
    filename: 'index.html'
    })
    ]
    }

    webpack.dev4.js

    let merge = require('webpack-merge')
    let base = require('./webpack.base4.js')

    module.exports = merge(base, {
    mode: 'development',
    devServer: {},
    devtool: 'source-map'
    })

    webpack.prod4.js

    let merge = require('webpack-merge')
    let base = require('./webpack.base4.js')

    module.exports = merge(base, {
    mode: 'production'
    })

    package.json

    "scripts": {
    "build": "webpack --config webpack.prod4.js",
    "dev": "webpack-dev-server --config webpack.dev4.js"
    },
    webpack 优化
    yarn add webpack webpack-cli html-webpack-plugin @babel/core babel-loader @babel/preset-env @babel/preset-react -D

    webpack.config.js

    let path = require('path')
    let HtmlWebpackPlugin = require('html-webpack-plugin')

    module.exports = {
    mode: 'development',
    entry: './src/index.js',
    output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
    },
    module: {
    rules: [
    {
    test: /.js$/,
    use: {
    loader: 'babel-loader',
    options: {
    presets: [
    '@babel/preset-env',
    '@babel/preset-react'
    ]
    }
    }
    },
    ]
    },
    plugins: [
    new HtmlWebpackPlugin({
    template: './src/index.html',
    filename: 'index.html'
    }),
    ]
    }

    优化:当某些包是独立的个体没有依赖
    以jquery为例,yarn add jquery -D,它是一个独立的包没有依赖,可以在webpack配置中,配置它不再查找依赖

    module: {
    noParse: /jquery/, // 不用解析某些包的依赖
    rules: [
    {
    test: /.js$/,
    use: {
    loader: 'babel-loader',
    options: {
    presets: [
    '@babel/preset-env',
    '@babel/preset-react'
    ]
    }
    }
    },
    ]
    }

    运行npx webpack

    从2057ms -> 1946 ms

    优化:规则匹配设置范围
    rules: [
    {
    test: /.js$/,
    exclude: '/node_modules/', // 排除
    include: path.resolve('src'), // 在这个范围内
    use: {
    loader: 'babel-loader',
    options: {
    presets: [
    '@babel/preset-env',
    '@babel/preset-react'
    ]
    }
    }
    }
    尽量实用include,不使用exclude,使用绝对路径

    优化:忽略依赖中不必要的语言包
    yarn add moment webpack-dev-server -D

    忽略掉moment的其他语言包

    let webpack = require('webpack')

    plugins: [
    new webpack.IgnorePlugin(/./locale/, /moment/)
    ]

    index.js

    import moment from 'moment'

    let r = moment().endOf('day').fromNow() // 距离现在多少天
    console.log(r);
    从 1.2MB 到 800kb

    动态链接库
    yarn add react react-dom

    正常使用

    webpack.config.js

    {
    test: /.js$/,
    exclude: '/node_modules/',
    include: path.resolve('src'),
    use: {
    loader: 'babel-loader',
    options: {
    presets: [
    '@babel/preset-env',
    '@babel/preset-react'
    ]
    }
    }
    }
    index.js

    import React from 'react'

    import {render} from 'react-dom'

    render(

    111111

    , window.root)
    index.html

    独立的将react react-dom 打包好, 打包好再引用,从而减少webpack每次都要打包react

    创建webpack.config.react.js

    let path = require('path')
    let webpack = require('webpack')
    module.exports = {
    mode: 'development',
    entry: {
    // test: './src/test.js'
    react: ['react', 'react-dom']
    },
    output: {
    filename: 'dll[name].js', // 产生的文件名
    path: path.resolve(__dirname, 'dist'),
    library: 'dll[name]', // 给输出的结果加个名字
    // libraryTarget: 'var' // 配置如何暴露 library
    // commonjs 结果放在export属性上, umd统一资源模块, 默认是var
    },
    plugins: [
    new webpack.DllPlugin({
    name: 'dll[name]', // name === library
    path: path.resolve(__dirname, 'dist', 'manifest.json') // manifest.json 定义了各个模块的路径
    })
    ]
    }
    libraryTarget

    manifest.json就是一个任务清单or动态链接库,在这个清单里面查找react

    npx webpack --config webpack.config.react.js

    在index.html增加引用

    在webpack.config.js 中配置,现在动态链接库manifest.json中查找,如果没有再打包react

    plugins: [
    new webpack.DllReferencePlugin({
    manifest: path.resolve(__dirname, 'dist', 'manifest.json')
    })
    ]

    DLLPlugin 和 DLLReferencePlugin

    npm run build

    打包后的bunle.js文件变小

    npm run dev

    可以理解为先把react打包,后面每次都直接使用react打包后的结果

    多线程打包happypack
    yarn add happypack

    webpack.config.js

    let Happypack = require('happypack')

    rules: [
    {
    test: /.js$/,
    exclude: '/node_modules/',
    include: path.resolve('src'),
    use: 'happypack/loader?id=js'
    },
    ]

    plugins: [
    new Happypack({
    id: 'js',
    use: [{
    loader: 'babel-loader',
    options: {
    presets: [
    '@babel/preset-env',
    '@babel/preset-react'
    ]
    }
    }]
    })
    ]
    js启用多线程,由于启用多线程也会浪费时间,因此当项目比较大的时候启用效果更好

    css启用多线程

    {
    test: /.css$/,
    use: 'happypack/loader?id=css'
    }

    new Happypack({
    id: 'css',
    use: ['style-loader', 'css-loader']
    }),
    webpack 自带的优化
    test.js

    let sum = (a, b) => {
    return a + b + 'sum'
    }

    let minus = (a, b) => {
    return a - b + 'minus';
    }

    export default {
    sum, minus
    }
    使用import
    index.js

    import calc from './test'

    console.log(calc.sum(1, 2));
    import在生产环境下会自动去除没有用的代码minus,这叫tree-shaking,将没有用的代码自动删除掉

    index.js

    let calc = require('./test')
    console.log(calc); // es 6导出,是一个default的对象
    console.log(calc.default.sum(1, 2));
    require引入es6 模块会把结果放在default上,打包build后并不会把多余minus代码删除掉,不支持tree-shaking

    作用域的提升
    index.js

    let a = 1
    let b = 2
    let c = 3
    let d = a + b + c

    console.log(d, '---------');
    打包出来的文件

    console.log(r.default.sum(1,2));console.log(6,"---------")
    在webpack中可以省略一些可以简化的代码

    抽取公共代码
    抽离自有模块
    webpack.config.js

    module.exports = {
    optimization: {
    splitChunks: { // 分割代码块,针对多入口
    cacheGroups: { // 缓存组
    common: { // 公共模块
    minSize: 0, // 大于多少抽离
    minChunks: 2, // 使用多少次以上抽离抽离
    chunks: 'initial' // 从什么地方开始, 从入口开始
    }
    }
    }
    },
    }
    SplitChunksPlugin

    分别有a.js和b.js, index.js和other.js分别引入a和b两个js

    index.js

    import './a'
    import './b'

    console.log('index.js');
    other.js

    import './a'
    import './b'

    console.log('other.js');
    webpack.config.js

    module.exports = {
    optimization: {
    splitChunks: { // 分割代码块,针对多入口
    cacheGroups: { // 缓存组
    common: { // 公共模块
    minSize: 0, // 大于多少抽离
    minChunks: 2, // 使用多少次以上抽离抽离
    chunks: 'initial' // 从什么地方开始, 从入口开始
    }
    }
    }
    },
    }
    抽离第三方模块
    比如jquery

    index.js 和 other.js分别引入

    import $ from 'jquery'

    console.log($);
    修改webpack.config.js配置:

    optimization: {
    splitChunks: { // 分割代码块,针对多入口
    cacheGroups: { // 缓存组
    common: { // 公共模块
    minSize: 0, // 大于多少抽离
    minChunks: 2, // 使用多少次以上抽离抽离
    chunks: 'initial' // 从什么地方开始,刚开始
    },
    vendor: {
    priority: 1, // 增加权重, (先抽离第三方)
    test: /node_modules/, // 把此目录下的抽离
    minSize: 0, // 大于多少抽离
    minChunks: 2, // 使用多少次以上抽离抽离
    chunks: 'initial' // 从什么地方开始,刚开始
    }
    }
    },
    },
    懒加载(延迟加载)
    yarn add @babel/plugin-syntax-dynamic-import -D

    source.js

    export default 'mayufo'
    index.js

    let button = document.createElement('button')
    button.innerHTML = 'hello'
    button.addEventListener('click', function () {
    console.log('click')
    // es6草案中的语法,jsonp实现动态加载文件
    import('./source.js').then(data => {
    console.log(data.default)
    })
    })
    document.body.appendChild(button)

    webpack.config.js

    {
    test: /.js$/,
    exclude: '/node_modules/',
    include: path.resolve('src'),
    use: [{
    loader: 'babel-loader',
    options: {
    presets: [
    '@babel/preset-env',
    '@babel/preset-react'
    ],
    plugins: [
    '@babel/plugin-syntax-dynamic-import'
    ]
    }
    }]
    }
    热更新(当页面改变只更新改变的部分,不重新打包)
    webpack.config.js

    plugins: [
    new HtmlWebpackPlugin({
    template: './src/index.html',
    filename: 'index.html'
    }),
    new webpack.NameModulesPlugin(), // 打印更新的模块路径
    new webpack.HotModuleReplacementPlugin() // 热更新插件
    ]
    index.js

    import str from './source'

    console.log(str);

    if (module.hot) {
    module.hot.accept('./source', () => {
    console.log('文件更新了');
    require('./source')
    console.log(str);
    })
    }

    tapable介绍 - SyncHook
    tapable

    webpack本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是Tapable,webpack中最核心的负责编译的Compiler和负责创建bundles的Compilation都是Tapable的实例。

    SyncHook 不关心监听函数的返回值

    yarn add tabable

    1.use.js

    let {SyncHook} = require('tapable') // 结构同步勾子

    class Lesson {
    constructor () {
    this.hooks = {
    // 订阅勾子
    arch: new SyncHook(['name']),
    }
    }
    start () {
    this.hooks.arch.call('may')
    }
    tap () { // 注册监听函数
    this.hooks.arch.tap('node', function (name) {
    console.log('node', name)
    })
    this.hooks.arch.tap('react', function (name) {
    console.log('react', name)
    })
    }
    }

    let l = new Lesson()

    l.tap(); //注册两个函数
    l.start() // 启动勾子

    1.theory.js

    class SyncHook { // 勾子是同步的
    constructor(args) { // args => ['name']
    this.tasks = []
    }
    tap (name, task) {
    this.tasks.push(task)
    }
    call (...args) {
    this.tasks.forEach((task) => task(...args))
    }
    }

    let hook = new SyncHook(['name'])

    hook.tap('react', function (name) {
    console.log('react', name);
    })

    hook.tap('node', function (name) {
    console.log('node', name);
    })

    hook.call('jw')
    tapable介绍 - SyncBailHook
    SyncBailHook为勾子加了个保险,当return返回不是undefine就会停止

    2.use.js

    let {SyncBailHook} = require('tapable') // 解构同步勾子

    class Lesson {
    constructor () {
    this.hooks = {
    // 订阅勾子
    arch: new SyncBailHook(['name']),

    }
    

    }
    start () {
    // 发布
    this.hooks.arch.call('may')
    }
    tap () { // 注册监听函数,订阅
    this.hooks.arch.tap('node', function (name) {
    console.log('node', name)
    return '停止学习' // 会停止
    // return undefined
    })
    this.hooks.arch.tap('react', function (name) {
    console.log('react', name)
    })
    }
    }

    let l = new Lesson()

    l.tap(); //注册两个函数
    l.start() // 启动勾子

    2.theory.js

    class SyncBailHook { // 勾子是同步的
    constructor(args) { // args => ['name']
    this.tasks = []
    }
    tap (name, task) {
    this.tasks.push(task)
    }
    call (...args) {
    let ret; // 当前函数的返回值
    let index = 0; // 当前要执行的第一个
    do {
    ret = this.tasksindex
    } while (ret === undefined && index < this.tasks.length)
    }
    }

    let hook = new SyncBailHook(['name'])

    hook.tap('react', function (name) {
    console.log('react', name);
    return '停止学习'
    // return undefined
    })

    hook.tap('node', function (name) {
    console.log('node', name);
    })

    hook.call('jw')

    tapable介绍 - SyncWaterfallHook
    SyncWaterfallHook上一个监听函数的返回值可以传给下一个监听函数

    3.use.js

    let {SyncWaterfallHook} = require('tapable') // 解构同步勾子

    // waterfall 瀑布

    class Lesson {
    constructor () {
    this.hooks = {
    // 订阅勾子
    arch: new SyncWaterfallHook(['name']),

        }
    }
    start () {
        // 发布
        this.hooks.arch.call('may')
    }
    tap () {   //  注册监听函数,订阅
        this.hooks.arch.tap('node', function (name) {
            console.log('node', name)
            return '学的不错'
        })
        this.hooks.arch.tap('react', function (name) {
            console.log('react', name)
        })
    }
    

    }

    let l = new Lesson()

    l.tap(); //注册两个函数
    l.start() // 启动勾子

    3.theory.js

    class SyncWaterfallHook { // 勾子是同步的 - 瀑布
    constructor(args) { // args => ['name']
    this.tasks = []
    }
    tap (name, task) {
    this.tasks.push(task)
    }
    call (...args) {
    let [first, ...others] = this.tasks;
    let ret = first(...args)
    others.reduce((a, b) => {
    return b(a);
    }, ret);

    }
    

    }

    let hook = new SyncWaterfallHook(['name'])

    hook.tap('react', function (name) {
    console.log('react', name);
    return 'react Ok'
    // return undefined
    })

    hook.tap('node', function (name) {
    console.log('node', name);
    return 'node Ok'
    })

    hook.tap('webpack', function (data) {
    console.log('webpack', data);
    })

    hook.call('jw')

    tapable介绍 - SyncLoopHook
    SyncLoopHook当监听函数被触发的时候,如果该监听函数返回true时则这个监听函数会反复执行,如果返回 undefined 则表示退出循环

    4.use.js

    let {SyncLoopHook} = require('tapable') // 解构同步勾子

    // 不返回undefined 会多次执行

    class Lesson {
    constructor () {
    this.index = 0
    this.hooks = {
    // 订阅勾子
    arch: new SyncLoopHook(['name']),

        }
    }
    start () {
        // 发布
        this.hooks.arch.call('may')
    }
    tap () {   //  注册监听函数,订阅
        this.hooks.arch.tap('node',  (name) => {
            console.log('node', name)
            return ++this.index === 3 ? undefined : '继续学'
        })
        this.hooks.arch.tap('react',  (name) => {
            console.log('react', name)
        })
    }
    

    }

    let l = new Lesson()

    l.tap(); //注册两个函数
    l.start() // 启动勾子

    4.theory.js

    class SyncLoopHook { // 勾子是同步的 - 瀑布
    constructor(args) { // args => ['name']
    this.tasks = []
    }
    tap (name, task) {
    this.tasks.push(task)
    }
    call (...args) {
    this.tasks.forEach(task => {
    let ret
    do {
    ret = task(...args);
    } while(ret !== undefined)
    })
    }
    }

    let hook = new SyncLoopHook(['name'])
    let total = 0
    hook.tap('react', function (name) {
    console.log('react', name);
    return ++total === 3 ? undefined: '继续学'
    })

    hook.tap('node', function (name) {
    console.log('node', name);
    })

    hook.tap('webpack', function (data) {
    console.log('webpack', data);
    })

    hook.call('jw')

    AsyncParallelHook 与 AsyncParallelBailHook
    异步的勾子分两种串行和并行

    并行等待所有并发的异步事件执行后执行回调

    注册的三种方法

    异步的注册方法tap
    异步的注册方法tapAsync, 还有个回调参数
    topPromise,注册promise
    调用的三种

    call (同步)
    callAsync (异步)
    promise (异步)
    这里介绍的是异步并行的

    AsyncParallelHook
    不关心监听函数的返回值。

    5.use.js

    let {AsyncParallelHook} = require('tapable') // 解构同步勾子

    // 不返回undefined 会多次执行

    class Lesson {
    constructor() {
    this.index = 0
    this.hooks = {
    // 订阅勾子
    arch: new AsyncParallelHook(['name']),

        }
    }
    
    start() {
        // 发布callAsync
        // this.hooks.arch.callAsync('may', function () {
        //     console.log('end');
        // })
        // 另一种发布promise
        this.hooks.arch.promise('may').then(function () {
                console.log('end');
            }
        )
    }
    
    tap() {   //  注册监听函数,订阅
        // 注册tapAsync
        // this.hooks.arch.tapAsync('node',  (name, callback) => {
        //     setTimeout(() => {
        //         console.log('node', name)
        //         callback()
        //     }, 1000)
        // })
        // this.hooks.arch.tapAsync('react',  (name, callback) => {
        //     setTimeout(() => {
        //         console.log('react', name)
        //         callback()
        //     }, 1000)
        // })
        // 另一种订阅 tapPromise
        this.hooks.arch.tapPromise('node', (name) => {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    console.log('node', name)
                    resolve()
                }, 1000)
            })
        })
        this.hooks.arch.tapPromise('react', (name) => {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    console.log('react', name)
                    resolve()
                }, 1000)
            })
        })
    }
    

    }

    let l = new Lesson()

    l.tap(); //注册两个函数
    l.start() // 启动勾子

    5.theory.js

    class AsyncParallelHook { // 勾子是同步的 - 瀑布
    constructor(args) { // args => ['name']
    this.tasks = []
    }

    tapAsync(name, task) {
        this.tasks.push(task)
    }
    
    tapPromise(name, task) {
        this.tasks.push(task)
    }
    callAsync(...args) {
        let finalCallback = args.pop()   // 拿出最终的函数
        let index = 0
        let done = () => {   // 类似promise.all的实现
            index++;
            if (index === this.tasks.length) {
                finalCallback();
            }
        }
        this.tasks.forEach(task => {
            task(...args, done) // 这里的args 已经把最后一个参数删掉
        })
    }
    
    promise(...args) {
        let tasks = this.tasks.map(task => task(...args))
        return Promise.all(tasks)
    }
    

    }

    let hook = new AsyncParallelHook(['name'])

    // hook.tapAsync('react', function (name, callback) {
    // setTimeout(() => {
    // console.log('react', name);
    // callback()
    // }, 1000)
    // })
    //
    // hook.tapAsync('node', function (name, callback) {
    // setTimeout(() => {
    // console.log('node', name);
    // callback()
    // }, 1000)
    // })

    // hook.tapAsync('webpack', function (name, callback) {
    // setTimeout(() => {
    // console.log('webpack', name);
    // callback()
    // }, 1000)
    // })

    hook.tapPromise('react', function (name, callback) {
    return new Promise((resolve, reject) => {
    setTimeout(() => {
    console.log('react', name);
    resolve()
    }, 1000)
    })
    })

    hook.tapPromise('node', function (name, callback) {
    return new Promise((resolve, reject) => {
    setTimeout(() => {
    console.log('node', name);
    resolve()
    }, 1000)
    })
    })

    //
    // hook.callAsync('jw', function () {
    // console.log('end');
    // })

    hook.promise('jw').then(function () {
    console.log('end');
    })

    AsyncParallelBailHook
    只要监听函数的返回值不为 null,就会忽略后面的监听函数执行,直接跳跃到callAsync等触发函数绑定的回调函数,然后执行这个被绑定的回调函数。

    使用和原理与SyncBailHook相似

    异步串行 —— AsyncSeriesHook
    串行 one by one

    6.use.js

    let {AsyncSeriesHook} = require('tapable') // 解构同步勾子

    class Lesson {
    constructor() {
    this.index = 0
    this.hooks = {
    // 订阅勾子
    arch: new AsyncSeriesHook(['name']),

        }
    }
    
    start() {
        // 发布
        // this.hooks.arch.callAsync('may', function () {
        //     console.log('end');
        // })
        // 另一种发布
        this.hooks.arch.promise('may').then(function () {
                console.log('end');
            }
        )
    }
    
    tap() {   //  注册监听函数,订阅
        // this.hooks.arch.tapAsync('node',  (name, callback) => {
        //     setTimeout(() => {
        //         console.log('node', name)
        //         callback()
        //     }, 1000)
        // })
        // this.hooks.arch.tapAsync('react',  (name, callback) => {
        //     setTimeout(() => {
        //         console.log('react', name)
        //         callback()
        //     }, 1000)
        // })
        // 另一种订阅
        this.hooks.arch.tapPromise('node', (name) => {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    console.log('node', name)
                    resolve()
                }, 1000)
            })
        })
        this.hooks.arch.tapPromise('react', (name) => {
            return new Promise((resolve, reject) => {
                setTimeout(() => {
                    console.log('react', name)
                    resolve()
                }, 1000)
            })
        })
    }
    

    }

    let l = new Lesson()

    l.tap(); //注册两个函数
    l.start(); // 启动勾子

    6.theory.js

    class AsyncSeriesHook { //
    constructor(args) { // args => ['name']
    this.tasks = []
    }

    tapAsync(name, task) {
        this.tasks.push(task)
    }
    
    tapPromise(name, task) {
        this.tasks.push(task)
    }
    
    callAsync(...args) {
        let finalCallback = args.pop()
        let index = 0;
        let next = () => {
            if (this.tasks.length === index) return finalCallback();
            let task = this.tasks[index++];
            task(...args, next);
        }
        next();
    }
    
    promise(...args) {
        // 将promise串联起来
        let [first, ...other] = this.tasks
        return other.reduce((p, n) => {
             return p.then(() => n (...args))
        }, first(...args))
    }
    

    }

    let hook = new AsyncSeriesHook(['name'])

    // hook.tapAsync('react', function (name, callback) {
    // setTimeout(() => {
    // console.log('react', name);
    // callback()
    // }, 1000)
    // })
    //
    // hook.tapAsync('node', function (name, callback) {
    // setTimeout(() => {
    // console.log('node', name);
    // callback()
    // }, 1000)
    // })
    //
    // hook.tapAsync('webpack', function (name, callback) {
    // setTimeout(() => {
    // console.log('webpack', name);
    // callback()
    // }, 1000)
    // })

    hook.tapPromise('react', function (name, callback) {
    return new Promise((resolve, reject) => {
    setTimeout(() => {
    console.log('react', name);
    resolve()
    }, 1000)
    })
    })

    hook.tapPromise('node', function (name, callback) {
    return new Promise((resolve, reject) => {
    setTimeout(() => {
    console.log('node', name);
    resolve()
    }, 1000)
    })
    })

    // hook.callAsync('jw', function () {
    // console.log('end');
    // })

    hook.promise('jw').then(function () {
    console.log('end');
    })

    异步串行 —— AsyncSeriesWaterfallHook
    上一个监听函数的中的callback(err, data)的第二个参数,可以作为下一个监听函数的参数

    7.use.js

    let {AsyncSeriesWaterfallHook} = require('tapable') // 解构同步勾子

    class Lesson {
    constructor() {
    this.index = 0
    this.hooks = {
    // 订阅勾子
    arch: new AsyncSeriesWaterfallHook(['name']),

        }
    }
    
    start() {
        // 发布
        this.hooks.arch.callAsync('may', function () {
            console.log('end');
        })
        // 另一种发布
        // this.hooks.arch.promise('may').then(function () {
        //         console.log('end');
        //     }
        // )
    }
    
    tap() {   //  注册监听函数,订阅
        this.hooks.arch.tapAsync('node',  (name, callback) => {
            setTimeout(() => {
                console.log('node', name)
                // callback(null, 'result')
                callback('error', 'result')   // 如果放error, 会跳过直接后面的勾子,直接走到最终的
    
            }, 1000)
        })
        this.hooks.arch.tapAsync('react',  (name, callback) => {
            setTimeout(() => {
                console.log('react', name)
                callback()
            }, 1000)
        })
        // 另一种订阅
        // this.hooks.arch.tapPromise('node', (name) => {
        //     return new Promise((resolve, reject) => {
        //         setTimeout(() => {
        //             console.log('node', name)
        //             resolve()
        //         }, 1000)
        //     })
        // })
        // this.hooks.arch.tapPromise('react', (name) => {
        //     return new Promise((resolve, reject) => {
        //         setTimeout(() => {
        //             console.log('react', name)
        //             resolve()
        //         }, 1000)
        //     })
        // })
    }
    

    }

    let l = new Lesson()

    l.tap(); //注册两个函数
    l.start(); // 启动勾子

    7.theory.js

    class AsyncSeriesWaterfallHook { //
    constructor(args) { // args => ['name']
    this.tasks = []
    }

    tapAsync(name, task) {
        this.tasks.push(task)
    }
    
    tapPromise(name, task) {
        this.tasks.push(task)
    }
    callAsync(...args) {
        let finalCallback = args.pop()
        let index = 0;
        let next = (err, data) => {
            let task = this.tasks[index]
            if(!task) return finalCallback();
            if (index === 0) {
                // 执行的第一个
                task(...args, next)
            } else {
                task(data, next)
            }
            index ++
        }
        next();
    }
    
    promise(...args) {
        // 将promise串联起来
        let [first, ...other] = this.tasks
        return other.reduce((p, n) => {
             return p.then((data) => n(data))
        }, first(...args))
    }
    

    }

    let hook = new AsyncSeriesWaterfallHook(['name'])

    // hook.tapAsync('react', function (name, callback) {
    // setTimeout(() => {
    // console.log('react', name);
    // callback(null, '结果1')
    // }, 1000)
    // })
    //
    // hook.tapAsync('node', function (name, callback) {
    // setTimeout(() => {
    // console.log('node', name);
    // callback(null, '结果2')
    // }, 1000)
    // })
    //
    // hook.tapAsync('webpack', function (name, callback) {
    // setTimeout(() => {
    // console.log('webpack', name);
    // callback()
    // }, 1000)
    // })

    //
    hook.tapPromise('react', function (name, callback) {
    return new Promise((resolve, reject) => {
    setTimeout(() => {
    console.log('react', name);
    resolve('result')
    }, 1000)
    })
    })

    hook.tapPromise('node', function (name, callback) {
    return new Promise((resolve, reject) => {
    setTimeout(() => {
    console.log('node', name);
    resolve()
    }, 1000)
    })
    })

    //
    //
    // hook.callAsync('jw', function () {
    // console.log('end');
    // })

    hook.promise('jw').then(function () {
    console.log('end');
    })

    手写webpack
    对应的may-pack项目

    yarn add webpack webpack-cli -D

    webpack.config.js

    let path = require('path')

    module.exports = {
    mode: 'development',
    entry: './src/index.js',
    output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
    }
    }
    npx webpack

    生成文件bundle.js

    (function (modules) {
    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;
    }
    
    
    // Load entry module and return exports
    return __webpack_require__(__webpack_require__.s = "./src/index.js");
    

    })
    ({
    "./src/a.js":
    (function (module, exports, webpack_require) {
    eval("let b = webpack_require(/*! ./base/b / "./src/base/b.js") module.exports = 'a'+ b //# sourceURL=webpack:///./src/a.js?");
    }),
    "./src/base/b.js":
    (function (module, exports) {
    eval("module.exports = 'b' //# sourceURL=webpack:///./src/base/b.js?");
    }),
    "./src/index.js":
    (function (module, exports, webpack_require) {
    eval(" let str = webpack_require(/
    ! ./a.js */ "./src/a.js") console.log(str); //# sourceURL=webpack:///./src/index.js?");
    })

    });

    新建项目用于自己的webpack,这里叫may-pack

    yarn init

    如果在node里想执行命令,创建bin文件,再创建may-pack.js

    配置package.json

    {
    "name": "may-pack",
    "version": "1.0.0",
    "main": "index.js",
    "license": "MIT",
    "bin": {
    "may-pack": "./bin/may-pack.js"
    }
    }
    may-pack.js

    ! /usr/bin/env node

    // node环境

    console.log('start');

    运行npm link将npm 模块链接到对应的运行项目中去,方便地对模块进行调试和测试

    在想运行may-pack的项目中运行,npm link may-pack 得到 start

    webpack分析及处理
    may-pack.js

    ! /usr/bin/env node

    // node环境

    console.log('start');

    let path = require('path')

    // 拿到配置文件webpack.config.js
    let config = require(path.resolve('webpack.config.js'));

    let Compiler = require('../lib/Compiler.js');

    let compiler = new Compiler(config);

    // 标识运行编译
    compiler.run()

    创建lib文件Compiler.js

    let path = require('path')
    let fs = require('fs')

    class Compiler {
    constructor(config) {
    // entry output
    this.config = config
    // 需要保存入口文件的路径
    this.entryId = ''; // './src/index.js'
    // 需要保存所有的模块依赖
    this.modules = {};
    this.entry = config.entry // 入口文件
    // 工作目录
    this.root = process.cwd(); // 当前运行npx的路径
    }

    // 构建模块
    buildModule(modulePath, isEntry) {
       
    }
    
    // 发射文件
    emitFile() {
        // 用数据 渲染想要的
    }
    
    run() {
        // 执行 创建模块的依赖关系
        this.buildModule(path.resolve(this.root, this.entry), true)  // path.resolve(this.root, this.entry) 得到入口文件的绝对路径
        // 发射打包后的文件
        this.emitFile()
    }
    

    }

    module.exports = Compiler

    主要两个任务

    拿到入口Id
    解析模块,也就是实现buildModule方法
    创建依赖关系
    may-pack中Compiler.js

    let path = require('path')
    let fs = require('fs')
    // babylon 主要把源码转成ast Babylon 是 Babel 中使用的 JavaScript 解析器。
    // @babel/traverse 对ast解析遍历语法树 负责替换,删除和添加节点
    // @babel/types 用于AST节点的Lodash-esque实用程序库
    // @babel/generator 结果生成

    let babylon = require('babylon')
    let traverse = require('@babel/traverse').default;
    let type = require('@babel/types');
    let generator = require('@babel/generator').default
    class Compiler {
    constructor(config) {
    // entry output
    this.config = config
    // 需要保存入口文件的路径
    this.entryId = ''; // './src/index.js'
    // 需要保存所有的模块依赖
    this.modules = {};
    this.entry = config.entry // 入口文件
    // 工作目录
    this.root = process.cwd(); // 当前运行npx的路径

    }
    // 拿到模块内容
    getSource (modulePath) {
        let content = fs.readFileSync(modulePath, 'utf8')
        return content
    }
    parse (source, parentPath) {
        console.log(source, parentPath)
    }
    // 构建模块
    buildModule(modulePath, isEntry) {
        // 拿到模块内容
        let source = this.getSource(modulePath)  // 得到入口文件的内容
        // 模块id modulePath(需要相对路径) = modulePath(模块路径) - this.root(项目工作路径)   src/index.js
        let moduleName = './' + path.relative(this.root, modulePath)
        console.log(source, moduleName);  // 拿到代码 和相对路径 ./src/index.js
        if (isEntry) {
            this.entryId = moduleName
        }
        let {sourceCode, dependencies} = this.parse(source, path.dirname(moduleName))   // ./src
        // 把相对路径和模块中的内容对应起来
        this.modules[moduleName] = sourceCode
    }
    
    // 发射文件
    emitFile() {
        // 用数据 渲染想要的
    }
    
    run() {
        // 执行 创建模块的依赖关系
        this.buildModule(path.resolve(this.root, this.entry), true)  // path.resolve(this.root, this.entry) 得到入口文件的绝对路径
        console.log(this.modules, this.entryId);
        // 发射打包后的文件
        this.emitFile()
    }
    

    }

    module.exports = Compiler

    ast递归解析
    parse方法主要靠解析语法树来进行转义 babylon 主要把源码转成ast Babylon 是 Babel 中使用的 JavaScript 解析器。 @babel/traverse 对ast解析遍历语法树 负责替换,删除和添加节点 @babel/types 用于AST节点的Lodash-esque实用程序库 @babel/generator 结果生成

    yarn add babylon @babel/traverse @babel/types @babel/generator

    may-pack中Compiler.js

    let path = require('path')
    let fs = require('fs')
    // babylon 主要把源码转成ast Babylon 是 Babel 中使用的 JavaScript 解析器。
    // @babel/traverse 对ast解析遍历语法树 负责替换,删除和添加节点
    // @babel/types 用于AST节点的Lodash-esque实用程序库
    // @babel/generator 结果生成

    let babylon = require('babylon')
    let traverse = require('@babel/traverse').default;
    let type = require('@babel/types');
    let generator = require('@babel/generator').default
    class Compiler {
    constructor(config) {
    // entry output
    this.config = config
    // 需要保存入口文件的路径
    this.entryId = ''; // './src/index.js'
    // 需要保存所有的模块依赖
    this.modules = {};
    this.entry = config.entry // 入口文件
    // 工作目录
    this.root = process.cwd(); // 当前运行npx的路径

    }
    // 拿到模块内容
    getSource (modulePath) {
        let content = fs.readFileSync(modulePath, 'utf8')
        return content
    }
    parse (source, parentPath) {
        // AST解析语法树
        let ast = babylon.parse(source)
        let dependencies = []; // 依赖的数组
        // https://astexplorer.net/
        traverse(ast, {
            // 调用表达式
            CallExpression(p) {
                let node = p.node; //对应的节点
                if(node.callee.name === 'require') {
                   node.callee.name = '__webpack_require__'
                    let moduledName = node.arguments[0].value   // 取到模块的引用名字
                    moduledName = moduledName + (path.extname(moduledName) ? '': '.js');  // ./a.js
                    moduledName = './' + path.join(parentPath, moduledName)  // './src/a.js'
                    dependencies.push(moduledName)
                    node.arguments = [type.stringLiteral(moduledName)] // 改掉源码
                }
            }
        })
        let sourceCode = generator(ast).code
        return { sourceCode, dependencies }
    }
    // 构建模块
    buildModule(modulePath, isEntry) {
        // 拿到模块内容
        let source = this.getSource(modulePath)  // 得到入口文件的内容
        // 模块id modulePath(需要相对路径) = modulePath(模块路径) - this.root(项目工作路径)   src/index.js
        let moduleName = './' + path.relative(this.root, modulePath)
        // console.log(source, moduleName);  // 拿到代码 和相对路径 ./src/index.js
        if (isEntry) {
            this.entryId = moduleName
        }
        // 解析把source源码进行改造, 返回一个依赖列表
        let {sourceCode, dependencies} = this.parse(source, path.dirname(moduleName))   // ./src
        // 把相对路径和模块中的内容对应起来
        this.modules[moduleName] = sourceCode
        dependencies.forEach(dep => {  // 附模块的加载 递归加载
            this.buildModule(path.join(this.root, dep), false)
        })
    }
    
    // 发射文件
    emitFile() {
        // 用数据 渲染想要的
    }
    
    run() {
        // 执行 创建模块的依赖关系
        this.buildModule(path.resolve(this.root, this.entry), true)  // path.resolve(this.root, this.entry) 得到入口文件的绝对路径
        console.log(this.modules, this.entryId);
        // 发射打包后的文件
        this.emitFile()
    }
    

    }

    module.exports = Compiler

    生成打包工具
    使用ejs模板

    may-pack中main.ejs

    (function (modules) {
    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;
    }

    // Load entry module and return exports
    return webpack_require(webpack_require.s = "<%-entryId %>");
    })({
    <% for(let key in modules){ %>
    "<%- key %>":
    (function (module, exports,webpack_require) {
    eval(<%-modules[key] %>);
    }),
    <% } %>
    });

    ejs入门

    yarn add ejs

    may-pack中Compiler.js

    let ejs = require('ejs')
    // 发射文件
    emitFile() {
    // 用数据 渲染想要的
    // 输出到那个目录下
    let main = path.join(this.config.output.path, this.config.output.filename)
    let templateStr = this.getSource(path.join(__dirname, 'main.ejs'))
    let code = ejs.render(templateStr, { entryId: this.entryId, modules: this.modules})
    this.assets = {}
    // 路径对应的代码
    this.assets[main] = code
    fs.writeFileSync(main, this.assets[main])
    }
    在webpack-training项目中运行npx may-pack, 得到bundle.js,运行得到结果

    增加loader
    创建loader文件夹,创建less-loader1.js和style-loader1.js

    yarn add less

    less使用

    less-loader1.js

    // 将less转为css
    let less = require('less')

    function loader(source) {
    let css = ''
    less.render(source, function (err, output) {
    css = output.css
    })
    css = css.replace(/ /g, ' ');
    return css
    }

    module.exports = loader

    style-loader1.js

    // 将css插入到html头部
    function loader(source) {
    console.log(111);
    let style = let style = document.createElement('style') style.innerHTML = ${JSON.stringify(source)} document.head.appendChild(style)
    return style
    }
    module.exports = loader

    // JSON.stringify(source) 可以将代码转为一行

    webpack.config.js

    let path = require('path')

    module.exports = {
    mode: 'development',
    entry: './src/index.js',
    output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
    },
    module: {
    rules: [
    {
    test: /.less$/,
    use: [
    path.resolve(__dirname, 'loader', 'style-loader1'),
    path.resolve(__dirname, 'loader', 'less-loader1')
    ]
    }
    ]
    }
    }

    创建index.less

    body {
    background: red
    }
    index.js

    let str = require('./a.js')

    require('./index.less')

    console.log(str);

    may-pack中Compiler.js

    // 拿到模块内容
    getSource (modulePath) {
    // 匹配各种文件的规则
    let rules= this.config.module.rules; // webpack.config.js 中rules的数组
    let content = fs.readFileSync(modulePath, 'utf8')

        for (let i = 0; i < rules.length; i++) {
            let rule = rules[i]
            let {test, use} = rule
            let len = use.length - 1
    
            if (test.test(modulePath)) {
                // console.log(use[len]);
                function normalLoader () {
                    // console.log(use[len--]);
                    let loader = require(use[len--])
                    content = loader(content)
                    // 递归调用loader 实现转化
                    if (len >= 0) {
                        normalLoader()
                    }
                }
                normalLoader()
            }
    
        }
        return content
    }
    

    运行npx may-pack

    增加plugins
    yarn add tapable

    may-pack中Compiler.js

    constructor(config) {
    // entry output
    this.config = config
    // 需要保存入口文件的路径
    this.entryId = ''; // './src/index.js'
    // 需要保存所有的模块依赖
    this.modules = {};
    this.entry = config.entry // 入口文件
    // 工作目录
    this.root = process.cwd(); // 当前运行npx的路径

        this.hooks = {
            entryOption: new SyncHook(),  // 入口选项
            compile: new SyncHook(),      // 编译
            afterCompile: new SyncHook(),  // 编译完成
            afterPlugins: new SyncHook(),   // 编译完插件
            run: new SyncHook(),         // 运行
            emit: new SyncHook(),        // 发射
            done: new SyncHook()         // 完成
        }
        // 如果传递了plugins参数
        let plugins = this.config.plugins
        if (Array.isArray(plugins)) {
            plugins.forEach(plugin => {
                plugin.apply(this); // 这里只是appLy方法不是改变this指向
            })
        }
        this.hooks.afterPlugins.call()
    }
    

    在webpack.config.js中写插件方法

    class P {
    apply(compiler) { // 这里只是appLy方法不是改变this指向
    // 绑定
    compiler.hooks.emit.tap('emit', function () {
    console.log('emit');
    })
    }
    }

    class P1 {
    apply(compiler) { // 这里只是appLy方法不是改变this指向
    // 绑定
    compiler.hooks.afterPlugins.tap('emit', function () {
    console.log('afterPlugins');
    })
    }
    }

    module.exports = {
    mode: 'development',
    entry: './src/index.js',
    output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
    },
    module: {
    rules: [
    {
    test: /.less$/,
    use: [
    path.resolve(__dirname, 'loader', 'style-loader'),
    path.resolve(__dirname, 'loader', 'less-loader')
    ]
    }
    ]
    },
    plugins: [
    new P(),
    new P1()
    ]
    }
    然后在各个地方调用

    may-pack中may-pack.js

    .....
    // 调用
    compiler.hooks.entryOption.call()
    // 标识运行编译
    compiler.run()
    may-pack中Compiler.js

    run() {
    this.hooks.run.call()

        this.hooks.compile.call()
        // 执行 创建模块的依赖关系
        this.buildModule(path.resolve(this.root, this.entry), true)  // path.resolve(this.root, this.entry) 得到入口文件的绝对路径
        // console.log(this.modules, this.entryId);
        this.hooks.afterCompile.call()
        // 发射打包后的文件
        this.emitFile()
        this.hooks.emit.call()
        this.hooks.done.call()
    }
    

    运行npx may-pack

    loader
    手写loader

    webapck.config.js

    let path = require('path')

    module.exports = {
    mode: 'development',
    entry: './src/index',
    output: {
    filename: 'build.js',
    path: path.resolve(__dirname, 'dist')
    },
    module: {
    rules: [
    {
    test: /.js/,
    use: 'loader1' // 如何找到这个loader1
    }
    ]
    },
    }

    创建loader文件loader1.js

    console.log(22);

    function loader(source) { // loader的参数就是源代码
    return source
    }
    module.exports = loader

    webpack.config.js

    let path = require('path')

    module.exports = {
    mode: 'development',
    entry: './src/index.js',
    output: {
    filename: 'build.js',
    path: path.resolve(__dirname, 'dist')
    },
    resolveLoader: {
    // 别名
    // alias: {
    // loader1: path.resolve(__dirname, 'loader', 'loader1')
    // }
    modules: ['node_modules', path.resolve(__dirname, 'loader')] // 先找node_modules, 再去loader中去找
    },
    module: {
    rules: [
    {
    test: /.js$/,
    // use: [path.resolve(__dirname, 'loader', 'loader1')]
    use: 'loader1' // 如何找到这个loader1

            },
            // {
            //     test: /.less$/,
            //     use: [
            //         path.resolve(__dirname, 'loader', 'style-loader'),
            //         path.resolve(__dirname, 'loader', 'less-loader')
            //     ]
            // }
        ]
    },
    

    }

    如何找到这个loader1

    通过配别名alias
    通过modules
    npx webpack

    配置多个loader
    数组方式
    先分别在loader文件下创建,loader2.js和loader3.js

    function loader(source) { // loader的参数就是源代码
    console.log('loader2'); // loader3.js 类似
    return source
    }
    module.exports = loader

    webpack.config.js

    rules: [
    {
    test: /.js$/,
    use: ['loader3', 'loader2', 'loader1']
    },
    ]
    运行npx webpack,分别打出

    loader1
    loader2
    loader3
    对象方式
    rules: [
    {
    test: /.js$/,
    use: ['loader3']
    },
    {
    test: /.js$/,
    use: ['loader2']
    },
    {
    test: /.js$/,
    use: ['loader1']
    }
    ]
    运行npx webpack,分别打出

    loader1
    loader2
    loader3
    loader的顺序: 从右到左, 从下到上

    也可以通过配置不同的参数改变loader的执行顺序,pre 前面的, post在后面的, normal正常

    {
    test: /.js$/,
    use: ['loader1'],
    enforce: "pre"
    },
    {
    test: /.js$/,
    use: ['loader2']
    },
    {
    test: /.js$/,
    use: ['loader3'],
    enforce: "post"
    },
    loader 带参数执行的顺序: pre -> normal -> inline -> post

    inline为行内loader

    在loader文件中新建inlin-loader

    function loader(source) { // loader的参数就是源代码
    console.log('inline');
    return source
    }
    module.exports = loader

    src/a.js

    module.exports = 'may'
    src/index

    console.log('hello')
    let srt = require('-!inline-loader!./a')
    -!禁用pre-loader和 normal-loader来处理了
    loader1
    loader2
    loader3
    inline
    loader3
    !禁用normal-loader
    loader1
    loader2
    loader3
    loader1
    inline
    loader3
    !! 禁用pre-loader、normal-loader、post-loader,只能行内处理
    loader1
    loader2
    loader3
    inline
    loader 默认由两部分组成pitch和normal

    user: [loader3, loader2, loader1]

    无返回值: 先执行pitch方法,从左到右,再获取资源

    pitch loader - 无返回值
    

    pitch loader3 → loader2 → loader1

    资源

    normal loader3 ← loader2 ← loader1
    有返回值: 直接跳过后续所有的loader包括自己的,跳到之前的loader, 可用于阻断

    loader

    user: [loader3, loader2, loader1]

    pitch loader - 有返回值
    

    pitch loader3 → loader2 loader1

    有返回值 资源

    normal loader3 loader2 loader1
    loadeer2.js

    function loader(source) { // loader的参数就是源代码
    console.log('loader2');
    return source
    }

    loader.pitch = function () {
    return '111'
    }
    module.exports = loader

    结果

    loader3
    babel-loader实现
    yarn add @babel/core @babel/preset-env

    webpack.config.js

    {
    test: '.js$/',
    use: {
    loader: 'babel-loader2',
    options: {
    presets: [
    '@babel/preset-env'
    ]
    }
    }
    }
    在loader文件创建babel-loader2.js(如果你已经装过babel-loader)

    拿到babel的参数

    yarn add loader-utils

    // 需要在webpack.config.js拿到babel的预设, 通过预设转换模块, 先引入babel
    let babel = require('@babel/core')

    // 拿到babel的参数 需要工具 loaderUtils
    let loaderUtils =require('loader-utils')

    function loader(source) { // loader的参数就是源代码 这里的this就是loader的上下文
    let options = loaderUtils.getOptions(this)
    console.log(this.resourcePath, 444); // [./src/index.js]
    let callback = this.async(); // babel的转换是异步的,同步的返回是不行的, 不能用return 同步就是直接掉用 异步会在async中
    babel.transform(source, {
    ...options,
    sourceMap: true, // 是否设置sourceMap 还需要再webpack.config.js 中配置 devtool: 'source-map'
    filename: this.resourcePath.split('/').pop() // 给生成的source-map指定名字
    }, function (err, result) {
    callback(err, result.code, result.map) // 异步 参数分别是「错误 转化后的代码 和 sourceMap」
    })
    console.log(options);
    // return source 失效
    }

    module.exports = loader

    index.js

    class May {
    constructor () {
    this.name = 'may'
    }
    getName () {
    return this.name
    }
    }

    let may = new May()

    console.log(may.getName());
    npx webpack

    banner-loader实现(自创)
    给所有匹配的js加一个注释

    webpack.config.js

    { // 给所有匹配的js加一个注释
    test: /.js$/,
    use: {
    loader: 'banner-loader',
    options: {
    text: 'may',
    filename: path.resolve(__dirname, 'banner.js')
    }
    }
    }
    banner.js

    二次星球中毒
    在loader文件创建banner-loader.js

    yarn add schema-utils 校验自己写的loader格式是否正确

    schema-utils

    banner-loader.js

    // 拿到loader的配置
    let loaderUtils = require('loader-utils')
    // 校验loader
    let validateOptions = require('schema-utils')
    // 读取文件
    let fs = require('fs') // 异步

    function loader(source) { // loader的参数就是源代码
    let options = loaderUtils.getOptions(this)
    let callback = this.async() // 读取文件是异步
    let schema = {
    type: 'object',
    properties: {
    text: {
    type: 'string'
    },
    filename: {
    type: 'string'
    }
    }
    }
    validateOptions(schema, options, 'banner-loader') // 自己的校验格式, 自己的写的配置, 对应的loader名字
    if (options.filename) {
    this.cacheable(false) // 不要缓存 如果有大量计算 推荐缓存
    // this.cacheable && this.cacheable()
    this.addDependency(options.filename) // 自动增加依赖
    fs.readFile(options.filename, 'utf8', function (err, data) {
    callback(err, /**${data}**/${source})
    })
    } else {
    callback(null, /**${options.text}**/${source})
    }
    return source
    }
    module.exports = loader

    优化:

    修改banner.js的内容后, webpack进行监控,打包webapck.config.js配置watch: true
    loader缓存
    实现file-loader和url-loader
    yarn add mime

    其主要用途是设置某种扩展名的文件的响应程序类型

    mime

    创建file-loader.js1

    // 拿到babel的参数 需要工具 loaderUtils
    let loaderUtils = require('loader-utils')

    function loader(source) { // loader的参数就是源代码
    // file-loader需要返回路径
    let filename = loaderUtils.interpolateName(this, '[hash].[ext]', {content: source })
    this.emitFile(filename, source) // 发射文件
    console.log('loader1');
    return module.exports="${filename}"
    }
    loader.raw = true // 二进制
    module.exports = loader

    创建url-loader1.js

    // 拿到babel的参数 需要工具 loaderUtils
    let loaderUtils = require('loader-utils')
    let mime = require('mime') // 途是设置某种扩展名的文件的响应程序类型

    function loader(source) { // loader的参数就是源代码
    let {limit} = loaderUtils.getOptions(this)
    console.log(this.resourcePath);
    if (limit && limit > source.length) {
    return module.exports="data:${mime.getType(this.resourcePath)};base64,${source.toString('base64')}"
    } else {
    return require('./file-loader1').call(this, source)
    }
    }
    loader.raw = true // 二进制
    module.exports = loader

    webpack.config.js

    {
    test: /.png$/,
    // 目的是根据图片生成md5 发射到dist目录下,file-loader 返回当前图片路径
    // use: 'file-loader'
    // 处理路径
    use: {
    loader: 'url-loader1',
    options: {
    limit: 200 * 1024
    }
    }
    }
    index.js引入图片

    import p from './photo.png'

    let img = document.createElement('img')
    img.src = p
    document.body.appendChild(img);

    less-loader和css-loader
    先安装less

    分别创建style-loader2 css-loader2 less-loader2

    style-loader1 与 less-loader1 同之前的

    css-loader
    主要用来处理css中的图片链接,需要把url转换成require

    webpack.config.js

    {
    test: /.png$/,
    // 目的是根据图片生成md5 发射到dist目录下,file-loader 返回当前图片路径
    // use: 'file-loader'
    // 处理路径
    use: {
    loader: 'url-loader1',
    options: {
    limit: 200 * 1024
    }
    }
    },
    {
    test: /.less$/,
    use: ['style-loader2', 'css-loader2', 'less-loader2']
    }
    创建index.less

    @base: #f938ab;
    body {
    background: @base;
    background: url("./photo.png");
    }
    less-loader2.js

    // 将less转为css
    let less = require('less')

    function loader(source) {
    let css = ''
    // console.log(source, 2222);
    less.render(source, function (err, output) {
    // console.log(output);
    css = output.css
    })
    // css = css.replace(/ /g, ' ');
    return css
    }

    module.exports = loader
    css-loader2.js

    // css-loader 用来解析@import这种语法,包括css中引入的图片
    function loader(source) {
    let reg = /url((.+?))/g // 匹配括号

    let pos = 0;
    let current;
    
    let arr = ['let list = []']
    
    while (current = reg.exec(source)) {
        let [matchUrl, g] = current   // matchUrl -> 'url("./photo.png")', g  -> '"./photo.png"'
        // console.log(matchUrl, g, 88);
        let lastIndex = reg.lastIndex - matchUrl.length    // 拿到css从开通到地址链接之前的index
        arr.push(`list.push(${JSON.stringify(source.slice(pos, lastIndex))})`)  // 拼入开始和地址之前的代码
        pos = reg.lastIndex
        arr.push(`list.push('url('+ require(${g}) +')')`)    // 拼入图片地址
    }
    arr.push(`list.push(${JSON.stringify(source.slice(pos))})`)  // 拼入地址到结尾的代码
    arr.push(`module.exports = list.join('')`)
    console.log(arr.join('
    '));
    // let list = []
    // list.push("body {\n  background: #f938ab;\n  background: ")
    // list.push('url('+ require("./photo.png") +')')
    // list.push(";\n}\n")
    // module.exports = list.join('')
    
    return arr.join('
    ')
    

    }
    module.exports = loader

    style-loader2.js

    let loaderUtils = require('loader-utils')

    // 将css插入到html头部
    function loader(source) {
    let str = let style = document.createElement('style') style.innerHTML = ${JSON.stringify(source)} document.head.appendChild(style)
    return str
    }

    // style-loader写了pitch,有返回后面的跳过,自己的写不会走
    loader.pitch = function (remainingRequest) { // 剩余的请求
    console.log(loaderUtils.stringifyRequest(this, '!!' + remainingRequest, 99999999))
    // 让style-loader 处理 less-loader 和css-loader拼接的结果
    // 得到 /Users/liuhuimin/work/webpack/loader/css-loader2.js!/Users/liuhuimin/work/webpack/loader/less-loader2.js!/Users/liuhuimin/work/webpack/src/index.less
    // 剩余的请求 less-loader!css-loader!./index.less
    // console.log(remainingRequest, 1223);
    // require返回的就是css-loader处理好的结果require('!!css-loader!less-loader!./index.less')
    let str = let style = document.createElement('style') style.innerHTML = require(${loaderUtils.stringifyRequest(this, '!!' + remainingRequest)}) document.head.appendChild(style)
    // stringifyRequest 绝对路径转相对路径
    return str
    }
    module.exports = loader

    user: ['style-loader2', 'css-loader2', 'less-loader2']

    pitch loader - 有返回值
    

    pitch style-loader2 → css-loader2 less-loader2

    有返回值 资源

    normal style-loader2 css-loader2 less-loader2
    在style-loader2中 引用了less-loader css-loader 和less文件

    webpack 中的插件
    yarn add webpack webpack-cil -D

    webpack.config.js

    let path = require('path')
    let DonePlugin = require('./plugins/DonePlugins')
    let AsyncPlugins = require('./plugins/AsyncPlugins')

    module.exports = {
    mode: 'development',
    entry: './src/index.js',
    output: {
    filename: 'build.js',
    path: path.resolve(__dirname, 'dist')
    },
    plugins: [
    new DonePlugin(), // 同步
    new AsyncPlugins() // 异步
    ]
    }

    node_modules/webpack/lib中查看Compiler.js

    同步plugins/DonePlugins
    打包完成

    class DonePlugins {
    apply (compiler) {
    console.log(1);
    compiler.hooks.done.tap('DonePlugin', (stats) => {
    console.log('编译完成');
    })
    }
    }

    module.exports = DonePlugins

    异步plugins/AsyncPlugins
    class AsyncPlugins {
    apply (compiler) {
    console.log(2);
    compiler.hooks.emit.tapAsync('AsyncPlugin', (complete, callback) => {
    setTimeout(() => {
    console.log('文件发射出来');
    callback()
    }, 1000)
    })
    compiler.hooks.emit.tapPromise('AsyncPlugin', (complete, callback) => {
    return new Promise((resolve, reject) => {
    setTimeout(() => {
    console.log('文件发射出来 222');
    resolve()
    }, 1000)
    })
    })
    }
    }

    module.exports = AsyncPlugins

    文件列表插件
    希望生成一个文件描述打包出来的文件

    在plugins中新建FileListPlugin

    class FileListPlugin {
    constructor ({filename}) {
    this.filename = filename
    }
    apply (compiler) {
    // 文件已经准备好了 要进行发射
    // emit
    compiler.hooks.emit.tap('FileListPlugin', (compilation) => {
    let assets = compilation.assets;
    console.log(assets, 55);
    let content = ## 文件名 资源大小
    // [ [bundls.js, {}], [index.html, {}]]
    Object.entries(assets).forEach(([filename, stateObj]) => {
    content += - ${filename} ${stateObj.size()}
    })
    // 资源对象
    assets[this.filename] = {
    source () {
    return content;
    },
    size () {
    return content.length
    }
    }
    })
    }
    }

    module.exports = FileListPlugin

    let path = require('path')
    let DonePlugin = require('./plugins/DonePlugins')
    let AsyncPlugins = require('./plugins/AsyncPlugins')
    let HtmlWebpackPlugin = require('html-webpack-plugin')
    let FileListPlugin = require('./plugins/FileListPlugin')

    module.exports = {
    mode: 'development',
    entry: './src/index.js',
    output: {
    filename: 'build.js',
    path: path.resolve(__dirname, 'dist')
    },
    plugins: [
    new DonePlugin(),
    new AsyncPlugins(),
    new HtmlWebpackPlugin({
    template: './src/index.html',
    filename: 'index.html'
    }),
    new FileListPlugin({
    filename: 'list.md'
    })
    ]
    }

    生成list.md

    内联的webpack插件
    新建index.css引入index.js

    yarn add css-loader mini-css-extract-plugin -D

    希望打包后css、js内联在index.html文件中

    创建plugins中InlineSourcePlugins.js

    yarn add --dev html-webpack-plugin@next

    HTML Webpack Plugin

    webpack.config.js

    let path = require('path')
    let DonePlugin = require('./plugins/DonePlugins')
    let AsyncPlugins = require('./plugins/AsyncPlugins')
    let HtmlWebpackPlugin = require('html-webpack-plugin')
    let FileListPlugin = require('./plugins/FileListPlugin')

    let InlineSourcePlugins = require('./plugins/InlineSourcePlugins')

    let MiniCssExtractPlugin = require('mini-css-extract-plugin')

    module.exports = {
    mode: 'production',
    entry: './src/index.js',
    output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
    },
    module: {
    rules: [
    {
    test: /.css$/,
    use: [MiniCssExtractPlugin.loader, 'css-loader']
    }
    ]
    },
    plugins: [
    // new DonePlugin(),
    // new AsyncPlugins(),
    new HtmlWebpackPlugin({
    template: './src/index.html',
    filename: 'index.html'
    }),
    new MiniCssExtractPlugin({
    filename: 'index.css'
    }),
    new InlineSourcePlugins({
    match: /.(js|css)/
    }),
    // new FileListPlugin({
    // filename: 'list.md'
    // })
    ]
    }

    InlineSourcePlugins.js

    const HtmlWebpackPlugin = require('html-webpack-plugin')

    // 把外链的标签编程内联的标签
    class InlineSourcePlugins {
    constructor({match}) {
    this.reg = match // 正则
    }

    // 处理某一个标签
    processTag(tag, compilation) {
        let newTag = {}
        let url = ''
        if (tag.tagName === 'link' && this.reg.test(tag.attributes.href)) {
            newTag = {
                tagName: 'style',
                attributes: {type: 'text/css'}
            }
            url = tag.attributes.href
        } else if (tag.tagName === 'script' && this.reg.test(tag.attributes.src)) {
            newTag = {
                tagName: 'script',
                attributes: {type: 'application/javascript'}
            }
            url = tag.attributes.src
        }
        if (url) {
            newTag.innerHTML = compilation.assets[url].source(); // 文件内容放到innerHTML属性中
            delete compilation.assets[url]   // 删除原有的资源
            return newTag
            // console.log(compilation.assets[url].source());
        }
        return tag
    }
    
    // 处理引入标签的数据
    processTags(data, compilation) {
        let headTags = []
        let bodyTags = []
        data.headTags.forEach(headTag => {
            headTags.push(this.processTag(headTag, compilation))
        })
        data.bodyTags.forEach(bodyTag => {
            bodyTags.push(this.processTag(bodyTag, compilation))
        })
        console.log({...data, headTags, bodyTags})
        return {...data, headTags, bodyTags}
    }
    
    
    
    apply(compiler) {
        // 通过webpackPlugin来实现  npm搜索  html-webpack-plugin
        compiler.hooks.compilation.tap('InlineSourcePlugins', (compilation) => {
            HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync(
                'alertPlugin',
                (data, callback) => {
                    // console.log('======');
                    // console.log(data) // 插入html标签的数据
                    // console.log('======');
                    data = this.processTags(data, compilation)   // compilation.assets 资源的链接
                    callback(null, data)
                })
        })
    
    }
    

    }

    module.exports = InlineSourcePlugins

    打包后自动发布
    打包好的文件自动上传致七牛

    需要这几个参数

    bucket: '' // 七牛的存储空间
    domain: '',
    accessKey: '', // 七牛云的两对密匙
    secretKey: '' // 七牛云的两对密匙
    注册七牛,并在对象存储里面,新建存储空间列表test,bucket: 'test'

    内容管理外链接默认域名 domain: 'xxxxxxxx'

    右上角个人面板里面个人中心,密钥管理分别对应accessKey和secretKey

    进入开发者中心 -> SDK&工具 -> 官方SDK -> Node服务端文档 —> 文件上传

    node文件上传

    npm install qiniu

    compiler-hooks

    webpack.config.js

    plugins: [
    new HtmlWebpackPlugin({
    template: './src/index.html',
    filename: 'index.html'
    }),
    new MiniCssExtractPlugin({
    filename: 'index.css'
    }),
    new UploadPlugin({
    bucket: 'test', // 七牛的存储空间
    domain: 'poyrjyh1b.bkt.clouddn.com',
    accessKey: 'xxxxxx', // 七牛云的两对密匙
    secretKey: 'yyyyyy' // 七牛云的两对密匙
    })
    ]
    UploadPlugin.js

    let qiniu = require('qiniu')
    let path = require('path')

    class UploadPlugin {
    constructor (options = {}) {
    // 参考 https://developer.qiniu.com/kodo/sdk/1289/nodejs
    let { bucket = '', domain = '', accessKey = '', secretKey = ''} = options
    let mac = new qiniu.auth.digest.Mac(accessKey, secretKey)
    let putPolicy = new qiniu.rs.PutPolicy({
    scope: bucket
    });
    this.uploadToken = putPolicy.uploadToken(mac)
    let config = new qiniu.conf.Config();
    this.formUploader = new qiniu.form_up.FormUploader(config)
    this.putExtra = new qiniu.form_up.PutExtra()
    }
    apply (compiler) {
    compiler.hooks.afterEmit.tapPromise('UploadPlugin', (complication) => {
    let assets = complication.assets
    let promise = []
    Object.keys(assets).forEach(filename => {
    promise.push(this.upload(filename))
    })
    return Promise.all(promise)
    })
    }

    upload (filename) {
        return new Promise((resolve, reject) => {
            let localFile = path.resolve(__dirname, '../dist', filename)
            this.formUploader.putFile(this.uploadToken, filename, localFile, this.putExtra, function(respErr,
                                                                                 respBody, respInfo) {
                if (respErr) {
                    reject(respErr)
                }
                if (respInfo.statusCode == 200) {
                    resolve(respBody)
                } else {
                    console.log(respInfo.statusCode)
                    console.log(respBody)
                }
            });
        })
    }
    

    }

    module.exports = UploadPlugin

  • 相关阅读:
    团队作业 总结
    个人作业 Alpha项目测试
    第二次作业
    交互式多媒体图书平台的设计与实现
    基于VS Code的C++语言的构建调试环境搭建指南
    码农的自我修养之必备技能 学习笔记
    工程化编程实战callback接口学习
    如何测评一个软件工程师的计算机网络知识水平和编程能力
    深入理解TCP协议及其源代码
    Socket与系统调用深度分析
  • 原文地址:https://www.cnblogs.com/Liovee/p/13237420.html
Copyright © 2011-2022 走看看