详解webpack打包

模块化打包工具的由来

  • ES Modules存在环境兼容问题
  • 模块文件过多,网络请求频繁
  • 所有的前端资源都需要模块化
  • 毋庸置疑,模块化是必要的

解决问题

  • 将es6等新功能变成es5等浏览器能识别的语言
  • 将很多个模块打包成一个bundle.js(在开发模式下用模块化开发 在生产模式下打包成一个bundle.js文件)

模块化打包工具概述

  • webpack 模块化打包工具
    • 模块加载器(Loader)
    • 代码拆分(Code splitting)
    • 资源模块(Asset Module)

上面的到爆工具解决的是前端整体的模块化并不单指Javascript模块化

webpack快速上手

  • 初始化 yarn init -y
  • webpack数据工具模块需要安装 yarn add webpack webpack-cli --dev
  • 配置webpack.config.js文件(运行在nodejs的文件所以要符合commjs)
const path = require('path');
module.exports={
    entry:'./src/main.js',  //文件入口
    output:{   //输出目录
        filname:'bundle.js',  //输出文件名称
        path:path.join(_dirname,'output')     //在outuput文件里面生成bundle.js文件      //指定输出文件的目录(需要绝对路径)
    }
}

webpack模式

  • none 最原始状态打包
  • production
  • development

webpack资源模块加载

借助loader可以加载任何形式的资源(loader对于同一个源可以依次使用多个loader,通过多个loader完成同一个功能。例如style-loader,css-loader)
dataUrl 可以用base64编码进行表示任何文件 url-loader

  • 解析css 模块 安装
    • yarn add style-loader --dev
    • yarn add css-loader --dev
  • 文件资源加载器 例如有些内容是不能转化成js的例如图片、字体 就需要使用file-loader
    • yarn add file-loader --dev
    • main.js中
  • 使用dataUrl加载任何文件
    • yarn add url-loader
    • 适合体积比较小的文件,因为这样减少请求次数,如果文件过大会影响打包速度 url-
    • 较大的文件使用file-loader单个文件存放,提高加载速度
    • 超过10kb文件单独提取存放
    • 小于10kb转换为dataUrl嵌入代码中
  • 使用babel-loader转化新特性
    • yarn add babel-loader @babel/core @babel/preset-env --dev
    • babel只是一个转化代码的平台,具体转化需要转化工具进行配置
    import icon from './icon.png'
    const img = new Image();
    img.src = icon;
    document.body.append(img);
    
  • 解析html(使用html-loader)
    • yarn add html-loader --dev
    <!-- footer.html -->
    
    <footer>
    <a href="better.png">download</a>
    </footer>
    
    <!-- main.js -->
    import footerHtml from './footer.html'
    documnet.write(footerHtml)
    
// 使用module:
module:{
    rules:[
        {
            test:'/.js$/',
            use: {
            loader:'babel-loader',
            options:{
                presets:['@babel/preset-env']
            }
            },
            
        },
        { //将css转换成js的模块
            test:'/.css$/',
            //这里需要注意 数组中的执行顺序是从右往左执行的 必须先解析css在加载style-loader
            use:['style-loader',css-loader']
        },
        {
           test:'/.png$',
          // use:'file-loader'url
          // use:'url-loader'
            use:{
                loader:'url-loader',
                options:{
                    limit:10*1024  //10kb
                }
            }
        },
        {
            test:'/.html$/',
            loader:'html-loader'
            //html中img src属性触发打包如果想让别的也触发可以进行如下配置
            options:{
                attrs:['img:src','a:href']
            }
        }
        
    ]
}
需要注意如果我们配置了url-loader 的大小限制,小于走url-loader否则走file-loader 但是需要注意的是卸载掉file-loader会报错(可不配置file-loader但是不能卸载)
webpack只是一个打包工具,如果你想处理代码编译转化的功能需要使用对应的loader进行转化

常用的加载器

* 编译转化加载类(css-loader)
* 文件操作类型加载器(file-loader)
* 代码检查加载器

webpack导入资源模块

  • 在使用的js中使用import ‘./main.css’

需要注意我们平时写的项目要求前后端分离 webpack建议我们根据代码的需要动态导入资源,真正需要资源的不是应用,而是代码,代码什么时候需要就要加载,代码更新不需要资源这个时候就不需要加载,webpack驱动了整个前端的应用。

  • 逻辑合理,js需要这些资源文件
  • 确保上线资源不缺失,都是必要的

webpack中模块的加载方式

  • 遵循ESmodules 标准的import
  • 支持commonJS标准的require函数 但需要注意如果载入一个ESmodule的defalut导出 需要使用
require('./header.js').default;
  • 支持AMD标准的define函数和require函数
  • 需要注意的是只建议在一个项目中使用一种引用方式,不然会增加维护成本
  • 样式代码中的@import指令和url函数
  • html代码中图片标签的src属性

webpack打包的工作原理

项目当中都会散落着各种各样的代码和资源文件,weboack会根据配置找到一个主入口文件一般都是.js的文件根据代码import export等依赖的模块和整个的模块依赖,形成依赖数,webpack会递归遍历依赖数,找到每个节点的所对应的资源文件,根据模块对应的ruls找到模块对应的加载器,然后交给对应的加载器加载模块,加载到的结果放到bundle.js文件中,实现项目的打包。loader是webpack的核心。

webpack loader的工作原理

自己开发一个markdown-loader,注意你可以使用多个loader但是最终返回的结果是一个javascript的代码

  • 安装一个 marked的解析模块
    • yarn add marked --dev
    • 导出的文件支持module.exports 和esModule的格式 export.defaule
    • 如果选择第二种方式需要安装处理html的loader add html-loader
markdown-loader.js

const marked = required('marked');
module.exports = source =>{
    const html = marked(source);
    
    // 第一种导出将 导出内容直接转化成javascript的格式
    //以下两种格式都支持导出
    <!--return `module.exports = ${JSON.stringify(html)}`
    return `export.defaule = ${JSON.stringify(html)}`
    -->
    
    //第二种格式 返回的html字符串直接交给下一个loader来处理
    return html;
    <!--console.log(source)-->
    <!--return source;-->
}

配置webpack.json
//返回javascript的格式
{
    test:'./md$/',
    use:'./markdown-loader'  //不仅可以使用模块还可以使用路径地址
}

//返回html的格式
{
    test:'./md$/',
    use:['html-loader','./markdown-loader']  //再次强调数组的执行顺序是从后面像前面执行的
}

webpack插件功能

loader处理资源 plugin处理那些非资源的内容实现了大多的功能

  • 自动清除目录的插件 clean-webpack-plugin
    • yarn add clean-webpack-plugin --dev
  • 自动生成html文件(如果不输出html文件 那么我就就需要将html引入的script路径手动指向打包的dist目录下相应用的路径地址如果打包的js文件名有所变化那我们就需要手动更改。)
    • yarn add html-webpack-plugin --dev
  • webpack动态生成html文件
    • html中可以自己创建 用loadsh的语法输出动态的内容
    <h1><%= htmlwebpackPlugin.options.title %></h1>
    
  • webpack多页面输出
  • webpack c处理不参与打包的静态文件需要复制到对应目录
    • yarn add copy-webpack-plugin --dev
const {CleanWebpackPlugin} = require('clean-webpack-plugin')  
const HtmlWebpackPlugin =  require('html-webpack-plugin')  //默认导出的插件类型
const CopyWebpackPlugin =  require('copy-webpack-plugin') 
plugins:[
    new CleanWebpackPlugin()
    <!--new HtmlWebpackPlugin()-->
    new HtmlWebpackPlugin({  //生成index.html
        title:'webpack',
        meta:{
            viewport:'width=device-width'
        },
        template:'./src/index.html'
    })
    //添加多个实例输出多个文件
    new HtmlWebpackPlugin({
        filename:'about.html'
    }),
    //接受的是一个数组,可以写路径,目录,通配符等
    new CopyWebpackPlugin([
    'public'
    //public/**
    ])
]

webpack开发一个插件

定义插件一个函数或者是一个包含apply方法对象

// 清除bundle.js中没用注释 emit在webpack输出文件时候执行
class MyPlugin {
    apply(compiler) {
        console.log('Myplugin');
        compiler.hooks.emit.tap('MyPlugin', compilation => {
            // compilation => 可以理解为此次打包的上下文
            for (const name in compilation.assets) {
                //文件的名称
                // console.log(compilation.assets[name].source())  //内容
                if (name.endsWith('.js')) {
                    const contents = compilation.assets[name].source();
                    console.log(contents);
                    const withoutComments = contents.replace(/\/\*\*+\*\//g, '');
                    // /******/   \/\*\*+\*\/
                    compilation.assets[name] = {  .//webpack必须要暴露出来的两个方法
                        source: () => withoutComments,
                        size: () => withoutComments.length
                    }
                }
            }
        })
    }
}

在plugin中new MyPlugin();

webpack增强开发体验 实现自动编译

  • webpack dev server 高度集成工具 自动编译,自动刷新浏览器
yarn add webpack-dev-server --dev 
// 通过yarn运行cli
yarn webpack-dev-server 运行命令会直接打包应用

*处理静态资源 webpack-dev-server

// 注释:上面代码中new CopyWebpackPlugin 可以实现资源的copy,但是一般new CopyWebpackPlugin都留在上线前的打包使用而已,因为在开发过程中每次打包都copy会影响速度。
module.exports = {
    devServer:{
        contentBase:['./public']  //指定静态资源
    }
}

webpack-dev-server 支持代理服务

devServer:{
   contentBase:'./public',
   proxy:{ 
   // 请求前缀
       '/api':{
            // http://localhost:8080/api/users -> https:api.geithub.com/api/users
           target:"https:api.github.com", //代理目标
           pathRewrite:{
               '^/api':''  //代理路径重写
           },
           //不能使用localhost:8080 作为请求GitHub的主机名
           changeOrign:true
       }
   }
}

source Map

  • 运行代码和源代码之间有很大差异。
  • 帮助开发者调试和定位错误
  • sourceMappingUrl = jquery-3.4.1.min.map 根据映射关系定位到源代码
  • 解决了源代码与运行代码不一致所导致的问题

如何配置source Map

devtool 开发过程中的辅助工具

  • 目前source Map支持12种
  • 每种方式的功效和效率都不相同

webpack配置source Map (eval模式下的source Map)

  • eval(‘console.log(123)’)
  • eval(‘console.log(123) //# sourceURL=./foo/’)
  • 将webpack.config.js中的属性设置为 devtool:‘eval’
  • 使用eval 构建速度最快 但是不知道错误的行内信息

模式

  • eval 将模块代码放到eval中但是不会生成对应的sourvcemao
  • eval-source-map 生成sourceMAP
  • cheap-eval-source-map 生成速度较快可以定义行错误信息不能定义到列,加工过后的结果,loader处理过后的结果
  • cheap-module-eval-source-map 能够定义到行 没有经过加工手写的源代码
  • inline-source-map和普通的相同inline-source-map以dataurl嵌入进来和eval相同 这个不常用因为体积较大
  • hidden-source-map在这个模式下代码并没有看到引用,但是确实是已经生成了
  • nosources-source-map 能看到错误信息,但是没有源代码开发过程中看不到源代码,为了在生产环境中不爆露源代码

webpack选择Source Map模式

  • 开发环境打包cheap-eval-source-map
原因:
个人编写代码风格每一行不会超过80个字符,所以定义到行就可以了
使用框架比较多,loader转化之后和之前都有很多差别,所以要选择有module
首次打包启动会慢,但是重写打包速度较快
  • none或者生产环境使用nosources-source-map
原因:
会暴露源代码
调试是开发阶段的事情,开发阶段尽量把所有的问题都找出来解决

## webpack dev server

webpack dev server刷新会对样式有影响,每次构建都需要刷新页面

HRM

  • 开启HRM 集成在webpack-dev-server 中
第一种
## webpack-dev-server --host

第二种
##devServer:{
    hot:true
    // 如果出错可以报错不会自动刷新 hot:only
}

const webpack = require('webpack');
plugins:[
new webpack.HotModuleReplacementPlugin();
]

//启动 yarn webpack-dev-server --open

HMR疑惑

  • webpack中的HRM需要手动配置
  • 样式文件可以自动处理,js脚本不可以

HMR API

module.hot.accept('./editor',()=>{
    
})

webpack 不同环境下的配置

  • 配置文件根据环境不同导出不同配置
module.exports = (env,argv){
    //env cli传过来的参数
    //argv 除了env之后的所有参数
    if(env ==='production'){
        config.mode = 'production'
        config.devtool = false
        config.plugins = [
         ...config.plugins,
         new CleanWebpackPlugin(),
         new CopywebpackPlugin(['public'])
        ]
    }
    return config
}

//运行 yarn webpack --env production
  • 一个环境对应一个配置文件
// 一般有三个文件 一个线上一个开发 一个公共文件

// ## webpack.prod.js
const common = require(./webpack.common)
const merge = require('webpack-merge')
const { CleanWebpackPlugin} = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')

module.exports = merge(common,{
    mode:'production',
    plugins:[
    new CleanWebpackPlugin(),
    new CopyWebpackPlugin(['public'])
    ]
})

//单独运行 yarn webpack --config webpack.prod.js

// ## webpack.dv.js

// ## webpack.common.js
// 公共配置
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    entry:'./src/main.js',
    output:{
        filename:'js/bundle.js'
    },
    module:{
        rules:[
            {
                test:'/.js$/',
                use: {
                loader:'babel-loader',
                options:{
                    presets:['@babel/preset-env']
                }
                },
                
            },
            { //将css转换成js的模块
                test:'/.css$/',
                //这里需要注意 数组中的执行顺序是从右往左执行的 必须先解析css在加载style-loader
                use:['style-loader',css-loader']
            },
            {
               test:'/.png$',
              // use:'file-loader'url
              // use:'url-loader'
                use:{
                    loader:'url-loader',
                    options:{
                        limit:10*1024  //10kb
                    }
                }
            },
            {
                test:'/.html$/',
                loader:'html-loader'
                //html中img src属性触发打包如果想让别的也触发可以进行如下配置
                options:{
                    attrs:['img:src','a:href']
                }
            }
            
        ]
    }
}

DefinePlugin

  • 为代码注入全局成员 默认启动 默认为全局注册了 process.env.NODE_ENV
const webpack = require('webpack');
PLUGINS :[
new webpack.DefinePlugin({
API_BASE_URL:'"https://api.example.com"'
//JSON.stringigy 如果生产和开发的值不一样可以通过这个来注入
})
]

tree-shaking

在生产模式下自动开启

集中配置webpack的优化功能

module.exports = [
    mode :'none',
    entry:'./src/index.js',
    output:{
        filename:'bundle.js'
    },
    optimization:{  //优化
    usedExports:true, //只标记使用的代码,
    minimize:true, //把这些无用的摇掉
        
    }
]

webpack 模块合并

optimization:{  //优化
   concatenatModules:true 
   //注释:平时打包会有很多模块函数,这样合并成一个,尽可能将所有的模块合并一个模块被称为 Scope Hoisting  作用域的提升,既提升了运行效率,又减少了代码体积
}

Tree-shaking & Babel

Tree-shaking前提是ES Modles,由webpack打包的代码必须使用ESM,babel-loader处理代码时有可能会把esmodule转化成了comminjs 所以 tree-shaking就不会生效 在最新版本中babel支持了tree-shaking原因是 源代码中并没有转化esmodule

//自己配置 如果使用commonjs这个时候就不会执行tree-shaking
{
    test:/\.js$/,
    use:{
        loader:'babel-loader',
        options:{
            presets:[['@babel/preset-env',{modules:'commonjs'}]
            //{modules:'commonjs'} 就不会执行 将modules:false就会禁止转化esmodule 所以会执行tree-shking
        }
    }
}

sideEffects 副作用

模块执行除了导出成员之外所做的其他的事情 一般用于npm包标记是否有副作用

  • 使用场景 在多模块开发中,加入我们将多个组件都放到index.js中引用 但是 我们在外部调用的时候只应用了其中的button组件 但是这个组件导入的是index.js这个时候打包会把所有的组件都打包进去这个时候我们就需要使用 optimization中的sideEffects
optimization:{
    sideEffects:true,  //打包看是否有标识看是否有副作用 如果没有则别移除
}

//在package.json中增加
"sideEffects":false  //标识当前项目没有副作用
  • 注意你的代码真的没有副作用 css都属于副作用
解决方法:
在package.json中
"sideEffects":['./extend.js','./extnd.css'] 数组进行配置

代码分割 Code Splitting

并不是每个模块在启动的时候都需要加载 要不然bundle的文件过大

  • 根据不同需求 可分为多入口打包
  • 动态导入按需加载

多页面打包 Multi Entry (多入口打包)

应用于多应用程序 一个页面提供一个打包入口 公共的可以提出

## 在entry 中进行配置多页面 记住是对象
entry:{
    index:'./src/index.js',
    album:'./src/album.js'
}

## 出口自动设置相应的文件
output:[name].bundle.js

## html中 new html-webpack-plgins中设置chunks:['index']另一个设置 chunks:['album']

公共模块的提取

多入口打包会存在公共的模块
例如index.js中和album.js中公共的模块使用了global.css的模块

optimization:{
    splitChunks:{
        chunks:'all'
    }
}

MiniCssExtractPlugin提取css单位件

  • 安装插件 yarn add mini-css-extract-plugin
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

//style-loader //将样式是通过style标签注入
MiniCssExtractPlugin.loader,//如果文件小于150kb可以不用单独提出来
css-loader //css 解析

压缩样式文件

  • 安装 yarn add optimize-css-assets-webpack-plugin --dev
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');

plugins:[
    new OptimizeCssAssetsWebpackPlugin(); //配资到plugins中 都可以使用
]

optimization:{
    minimizer:[
    new OptimizeCssAssetsWebpackPlugin();  //配置到这里 在没有开启压缩的时候插件就不会工作
    //需要注意的是配置了minimizer 这个时候打包默认手动填写,需要配置js打包压缩不然js无法压缩
    new TerserWebpackPlugin()  //需要安装和引用
    ]
}

const TerserWebpackPlugin = ('terser-webpack-plugin')

输出文件名Hash

在生产模式下,文件名使用hash

output:{
    //针对项目,有一个发生改变就会重新生成
    filename:'[name]-[hash].bundle.js'
    // 打包过程中同一类,改动哪里那个类js.css都会改变
    filename:'[name]-[chunkhash].bundle.js'
    //解决缓存最好的方法 每个文件都有hashcontenthash:8 也可以指定位数 默认20
    filename:'[name]-[contenthash].bundle.js'
}

注释

  • output 输出的必须是绝对路径

  • webpack加载资源的方式:

    • 遵循ES Modules标准的import声明
    • 遵循CommonJS标准的require函数
    • 遵循AMD标准的define函数和require函数
    • 样式代码中的@import指令和url函数
    • HTML代码中图片标签的src属性
  • webpack本身只能打包js文件,但对于其他资源例如css,图片,或者其他的语法集比如jsx,是没有办法加载的,这就需要对应的loader将资源转化,加载进来

  • TreeShaking在生产模式会自动开启,他是一组功能搭配使用后的效果,起哄usedExports负责标记(枯树叶),sideEffects一般标记npm包标记是否有副作用

vs code折叠快捷键 control+k control+0

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值