美文网首页
webpack4从入门到掌握核心内容

webpack4从入门到掌握核心内容

作者: FTD止水 | 来源:发表于2019-04-23 22:59 被阅读0次

    webpack4基础篇

    为什么要学习webpack?

    随着web前端发展越来越快,项目也变的越加复杂,这时我们就要通过模块引入的方式来构建我们的前端项目,一旦引入的模块数量过多,维护和管理这些模块也就变得很困难。这时,我们就要借助工具来维护这些模块,而webpack就是我们所需要的工具(当然还有很多其它工具,如:gulp,grunt,browserify等)。学会了webpack,也就学会前端工程化的内容,也能让我们从工程化角度来构建我们的项目,这样才会大大提高面试的议价能力。

    webpack的js编译功能初探

    在使用webpack的编译功能之前,电脑一定要安装nodejs、git(个人习惯用gitbash的命令行)。然后我们在编译器中创建一个项目名为webpack的文件夹来存放我们的学习项目,项目目录和文件结构如下图: 项目目录结构.png

    A.js文件代码如下:

    export default function(){
        alert("方法执行了")
    }
    

    index.js代码如下:

    import A from "./A.js";
    A();
    

    index.html代码如下:

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8" />
            <title>webpack-study</title>
        </head>
        <body>
            <div>webpack-study</div>
            <script src="js/index.js" type="text/javascript" charset="utf-8"></script>
        </body>
    </html>
    
    以上代码就是使用ES6的模块导出和引入功能,完成代码之后我们在浏览器中运行index.html 浏览器运行结果.png

    发先报错了,因为浏览器并不能识别ES6的模块功能语法,怎么办呢?我们可以借助webpack里面的一个小功能,它可以帮我们编译这段js代码,让浏览器能够知道我们在做什么。

    首先进入到项目的根目录中,单击右键,左键点击Git Bash Here,调出gitbash命令行工具,如下图: gitbash命令工具.png
    然后在命令行中分别输入如下命令:
    //初始化项目,成功之后会在项目根目录中多出一个package.json的文件
    npm init
    //(分别安装webpack-cli、webpack)
    cnpm install webpack-cli --save-dev
    cnpm install webpack --save
    //cnpm 为国内淘宝镜像,安装速度快,如何配置cnpm请自行百度或者看我写的[axios在Vue-cli+webpack环境中的使用(扫盲篇)](https://www.jianshu.com/p/97fea7c54969)里面有介绍
    

    在项目中安装好了webpack工具之后,我们就可以使用npx命令来编译我们之前写的index.js代码(编译成浏览器能够看懂的代码),在命令行中输入如下命令:

    npx webpack js/index.js
    

    编译成功之后,会在在项目里多出来一个dist文件夹,里面有一个main.js文件,这时我们修改我们之前写过的index.html文件,修改如下(之前引入的是index.js文件,现在引入的是dist目录下的main.js文件):

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8" />
            <title>webpack-study</title>
        </head>
        <body>
            <div>webpack-study</div>
            <!--<script src="js/index.js" type="text/javascript" charset="utf-8"></script>-->
            <script src="dist/main.js" type="text/javascript" charset="utf-8"></script>
        </body>
    </html>
    
    然后运行index.html代码,结果如下: 执行结果.png

    这样webpack就帮我们实现了ES6模块语法的编译功能(也仅限于模块语法的编译),当然webpack还有很多其它功能,将在后面介绍,敬请期待。
    2019/4/23

    webpack的核心功能

    webpack的核心功能就是模块打包工具,在官方网站上已有明确的说明。上面例子使用了ES6模块功能,webpack只是将这些模块都打包到一起(打包到main.js文件),这样就更好理解了。webpack是模块打包工具,所以它能够识别任何模块引入的语法(如ES6模块引入、CommonJS模块引入语法、CMD、ADM),帮我们打包成浏览器可识别的代码。下面再介绍一个CommonJS模块引入语法,在之前的webpack项目中删除dist这个文件夹,然后修改index.js和A.js。
    A.js修改之后代码如下:

    module.exports=function(){
        alert("方法执行了")
    }
    

    index.js修改之后代码如下:

    let A=require('./A.js')
    A();
    

    然后再项目根目录中运行gitbash,在命令行中输入:

    npx webpack js/index.js
    //将index.js文件进行打包
    

    打包成功后,运行index.html,结果如下:

    执行结果.png
    所以说webpack是一个js编译器是不准确的,确切的说webpack是模块打包工具。webpack不仅能够打包js文件,还可以打包更多类型的文件如:css、jpg、png等,这些将在后面介绍。

    如何正确安装webpack?

    首先要保证电脑已经安装了nodeJS和git(安装git是因为我要使用gitbash命令行工具,个人习惯,也可不安装,使用windows的终端命令行工具)。首先webpack这里极为不推荐全局安装,因为不同的项目使用的webpack版本号可能不一致,如果全局安装,可能会导致因版本号不一致而无法运行项目。我们先创建一个webpack-demo的文件夹,点击进入这个文件夹,然后右键,点击Git Bash Here调出命令行,在命令行中输入:

    //初始化项目
    npm init
    

    然后根据提示一路回车就可以,初始化完毕之后,项目目录中会多出一个package.json文件,这个文件主要存放一些项目的相关信息。我们需要在项目中安装webpack,如果想安装指定的版本号,可以输入如下命令来查看都有哪些版本号:

    npm info webpack
    

    安装指定的webpack版本可以输入如下命令:

    cnpm install webpack@版本号 webpack-cli -D
    

    我这里用的是默认当前版本cnpm install webpack webpack-cli -D
    注意:只有安装了webpack-cli才能在命令行使用webpack命令
    安装成功之后,我们在命令行中输入如下命令:

    //查看这个项目的webpack使用的版本号
    webpack -v
    

    发先命令不起作用,这时怎么回事呢?是因为webpack这个命令是在全局中查找,我们的webpack并没有全局安装,而node刚好为我们提供了如何查看项目中webpack版本的命令,输入如下命令:

    //查看这个项目的webpack使用的版本号
    npx webpack -v
    

    发先版本号可以成功的查询出来。

    如何使用webpack的配置文件?

    当我们使用webpack进行文件打包的时候,不同类型的文件打包的结果和方式肯定是不一样的。我们要打包哪个文件(入口文件),打包出的文件放在哪里,这些webpack是不知道的,所以我们要进行配置。我们可以在项目根目录中创建一个webpack.config.js的配置文件,然后编写代码:

    //引入一个node路径有关的核心模块,用于配置输出文件的路径
    const path=require('path');
    module.exports={
        mode:'production',//打包出的文件格式
        //入口文件,也就是webpack要打包的文件
        entry:'./js/index.js',
        output:{
            filename:'bundle.js',//打包后的文件名
            path:path.resolve(__dirname,'dist'),//打包后文件所在的路径,根目录的dist文件夹,__dirname代表项目根目录
        }
    }
    

    在项目目录下gitbash中运行如下命令:

    npx webpack
    //因为在配置文件中已经配置了打包哪个文件,所以这里不用指明打包哪个文件
    
    目录.png

    如上图所示,我们已经成功打包了。
    我们在使用vue-cli脚手架的时候,打包文件并没有使用npx webpack这个命令,那我们想要像vue-cli脚手架那样打包我们的项目该怎么做呢?我们就要打开package.json文件,去配置一些命令(配置scripts),代码如下:

    {
      "name": "webpack",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "bundle": "webpack" //意思是运行bundle命令时,就相当于运行webpack命令
      },
      "author": "",
      "license": "ISC",
      "devDependencies": {
        "webpack": "^4.30.0",
        "webpack-cli": "^3.3.1"
      }
    }
    

    配置完成之后,我们就可以不用使用npx webpack这个指令了,我们就可以运行npm run bundle 这个指令来进行打包。
    2019/4/24

    什么是Loader

    我们知道webpack是模块打包工具,可以打包任何类型的文件,那现在就可以尝试一下如何打包图片文件。接着之前的项目来做,首先在项目目录中创建img文件夹,用于存放图片,然后在里面存入一张png图片,名为logo.png,然后修改index.js代码如下:

    let A=require('./A.js')
    let imgSrc=require('../img/logo.png');
    let img=new Image();
    img.src=imgSrc;
    document.getElementById('root').append(img)
    A();
    

    在项目中运行gitbash命令行工具,执行npm run bundle,这时发先打包失败了,命令行报错,这是因为webpack只知道如何打包js文件并不知道其它类型的文件该如何打包,那么就要编写配置文件来告诉webpack该如何打包文件,这个时候就要用到loader,因为只有loader知道如该何打包一个文件,添加一个module模块打包配置项,如下:

    //引入一个node路径有关的核心模块,用于配置输出文件的路径
    const path=require('path');
    module.exports={
        mode:'production',//打包出的文件格式
        //入口文件,也就是webpack要打包的文件
        entry:'./js/index.js',
        output:{
            filename:'bundle.js',//打包后的文件名
            path:path.resolve(__dirname,'dist'),//打包后文件所在的路径,根目录的dist文件夹,__dirname代表项目根目录
        },
        module:{
            rules:[{
                test:/\.png$/,
                use:{
                    loader:'file-loader'
                }
            }]
        }
    }
    

    配置好了之后,我们就要安装这个loader

    cnpm install file-loader -D  //安装file-loader
    npm run bundle  //打包项目
    

    这样就打包成功了,然后查看dist目录,发先多了一个图片文件,点击这个图片,发先正好是我们打包的图片,我么们在dist目录下创建一个名为index的html文件作为入口文件,然后编辑index.html代码如下:

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8" />
            <title>webpack-study</title>
        </head>
        <body id="root">
            <div>webpack-study</div>
            <script src="bundle.js" type="text/javascript" charset="utf-8"></script>
        </body>
    </html>
    

    运行index.html,发先页面已经成功加载出图片。
    现在我要打包png、jpg、gif文件,而且打包后的名字要和打包之前的文件名一直,并把这些图片都打包到dist文件夹中的images目录中去该怎么办呢,依然是要配置webpack,代码如下:

    //引入一个node路径有关的核心模块,用于配置输出文件的路径
    const path=require('path');
    module.exports={
        mode:'production',//打包出的文件格式
        //入口文件,也就是webpack要打包的文件
        entry:'./js/index.js',
        output:{
            filename:'bundle.js',//打包后的文件名
            path:path.resolve(__dirname,'dist'),//打包后文件所在的路径,根目录的dist文件夹,__dirname代表项目根目录
        },
        module:{
            rules:[{
                test:/\.(png|jpg|gif)$/,//打包文件是以png,jpg,gif结尾的正则匹配
                use:{
                    loader:'file-loader',
                    options:{
                        name:'[name].[ext]',//按原有名字打包
                        outputPath:'images/',//打包到dist下的miages文件夹里
                    }
                }
            }]
        }
    }
    

    下面再介绍一个根file-loader很类似的loader,叫做url-loader,我们先安装url-loader,在命令行中输入

    cnpm install url-loader -D
    

    然后修改配置文件,如下:

    //引入一个node路径有关的核心模块,用于配置输出文件的路径
    const path=require('path');
    module.exports={
        mode:'production',//打包出的文件格式
        //入口文件,也就是webpack要打包的文件
        entry:'./js/index.js',
        output:{
            filename:'bundle.js',//打包后的文件名
            path:path.resolve(__dirname,'dist'),//打包后文件所在的路径,根目录的dist文件夹,__dirname代表项目根目录
        },
        module:{
            rules:[{
                test:/\.(png|jpg|gif)$/,//打包文件是以png,jpg,gif结尾的正则匹配
                use:{
                    loader:'url-loader',
                    options:{
                        name:'[name].[ext]',//按原有名字打包
                        outputPath:'images/',//打包到dist下的miages文件夹里
                        limit:2048,//图片小于2kb时,图片会以base64存在,大于2kb则会打包成图片文件
                    }
                }
            }]
        }
    }
    

    然后运行npm run bundle 命令,打包成功之后,我们发先dist文件夹中并没有找到图片文件,这是怎么回事?先运行index.html入口文件,发现图片可以显示在浏览器中,我们打开控制台,查看该图片,发现这个图片是以base64格式存在的。这是因为url-loader会把图片转化成base64格式存放我代码中,这种打包能减少图片的http请求,但是如果图片特别大,打包生成的js文件也就会特别大,那加载js的时间也就非常长,会出现一开始白屏的状态,所以url-loader处理小图片是非常适合的。
    2019/4/25

    如何使用loader打包静态资源

    以css样式文件为例,在项目目录中新建css文件夹,然后在里面创建index.css文件,代码如下:

    .image{
        height: 100px;
        width: 100px;
    }
    

    修改之前项目index.js文件,代码如下:

    import '../css/index.css'//引入css文件
    let A=require('./A.js')
    let imgSrc=require('../img/logo.png');
    let img=new Image();
    img.src=imgSrc;
    img.classList.add("image");//给img标签添加类名
    
    document.getElementById('root').append(img)
    A();
    

    代码写好了之后,我们看看css文件是否能成功打包,在gitbash中运行npm run bundle,发现报错了,webpack没有找到index.css文件,这时因为webpack不知道该如何打包css文件,所以就要借助loader。先打开webpack配置文件,修改代码如下:

    //引入一个node路径有关的核心模块,用于配置输出文件的路径
    const path=require('path');
    module.exports={
        mode:'production',//打包出的文件格式
        //入口文件,也就是webpack要打包的文件
        entry:'./js/index.js',
        output:{
            filename:'bundle.js',//打包后的文件名
            path:path.resolve(__dirname,'dist'),//打包后文件所在的路径,根目录的dist文件夹,__dirname代表项目根目录
        },
        module:{
            rules:[{
                test:/\.(png|jpg|gif)$/,//打包文件是以png,jpg,gif结尾的正则匹配
                use:{
                    loader:'url-loader',
                    options:{
                        name:'[name].[ext]',//按原有名字打包
                        outputPath:'images/',//打包到dist下的miages文件夹里
                        limit:2048,//图片小于2kb时,图片会以base64存在,大于2kb则会打包成图片文件
                    }
                }
            },
            {
                test:/\.css$/,//打包css的loader配置
                use:['style-loader','css-loader']
            }
            ]
        }
    }
    

    然后安装style-loader,css-loader,命令行执行如下命令:

    cnpm install style-loader css-loader -D
    

    安装好了之后打包npm run bundle,运行index.html,发现css已经被成功打包。
    现在分析一下这两个loader的作用,css-loader的用处是帮助我们分析出几个css文件的引用关系(如@import './xxx.css'),style-loader是帮助我们把打包后的css挂载到header中style里,所以css打包需要这两个loader相互配置才能成功。
    有些时候我们需要使用scss来简化项目中的css,那scss文件该如何打包呢?首先我们先修改index.css,把它改成index.scss,里面的代码也做如下修改:

    body{
        .image{
            height: 100px;
            width: 100px;
        }   
    }
    

    修改index.js代码如下:

    import '../css/index.scss'//引入css文件
    let A=require('./A.js')
    let imgSrc=require('../img/logo.png');
    let img=new Image();
    img.src=imgSrc;
    img.classList.add("image");//给img标签添加类名
    
    document.getElementById('root').append(img)
    A();
    

    修改配置文件,代码如下:

    //引入一个node路径有关的核心模块,用于配置输出文件的路径
    const path=require('path');
    module.exports={
        mode:'production',//打包出的文件格式
        //入口文件,也就是webpack要打包的文件
        entry:'./js/index.js',
        output:{
            filename:'bundle.js',//打包后的文件名
            path:path.resolve(__dirname,'dist'),//打包后文件所在的路径,根目录的dist文件夹,__dirname代表项目根目录
        },
        module:{
            rules:[{
                test:/\.(png|jpg|gif)$/,//打包文件是以png,jpg,gif结尾的正则匹配
                use:{
                    loader:'url-loader',
                    options:{
                        name:'[name].[ext]',//按原有名字打包
                        outputPath:'images/',//打包到dist下的miages文件夹里
                        limit:2048,//图片小于2kb时,图片会以base64存在,大于2kb则会打包成图片文件
                    }
                }
            },
            {
                test:/\.scss$/,//打包scss的loader配置
                use:['style-loader','css-loader','sass-loader']
            }
            ]
        }
    }
    

    然后通过查阅相关文档得知打包scss文件要安装sass-loader和node-sass,在命令行输入如下命令进行安装:

    cnpm install sass-loader node-sass -D
    

    然后运行index.html,发现样式已经成功引入了。loader的引入是有先后顺序的,执行顺序是从下到上,从右到左,所以当我们打包一个scss文件时,先通过sass-loader解析scss文件,让他编译成css文件,然后通过css-loader将之打包到项目中,最后通过style-loader将打包好的css样式挂载到style标签中。
    接下来再看如何通过webpack处理css样式浏览器内核前缀,也就是通过loader帮我们自动添加内核前缀,先在之前的配置文件scss loader部分修改如下:

    use:['style-loader','css-loader','sass-loader','postcss-loader']
    

    然后在命令行安装postcss-loader和autoprefixer

    cnpm install postcss-loader -D
    cnpm install autoprefixer -D
    

    安装成功之后,在项目根目录中创建一个postcss.config.js文件,配置一个postcss-loader要使用的一个插件,代码如下:

    module.exports={
        plugins:[
            require('autoprefixer')
        ]
    }
    

    这些文件都配置和安装好了之后,我们修改index.scss文件,代码如下:

    body{
        .image{
            height: 100px;
            width: 100px;
            transform: translate(200px,200px);
            user-select: none;
        }   
    }
    

    然后打包文件,运行index.html,在浏览器中打开代码调试,发现那些需要带浏览器内核前缀的css样式已经自动为我们添加好了。
    注意事项:当scss文件有引入关系时,也就是scss文件中有@import 'xxx.scss'代码时,我们需要修改配置文件中css-loader如下:

    //引入一个node路径有关的核心模块,用于配置输出文件的路径
    const path=require('path');
    module.exports={
        mode:'production',//打包出的文件格式
        //入口文件,也就是webpack要打包的文件
        entry:'./js/index.js',
        output:{
            filename:'bundle.js',//打包后的文件名
            path:path.resolve(__dirname,'dist'),//打包后文件所在的路径,根目录的dist文件夹,__dirname代表项目根目录
        },
        module:{
            rules:[{
                test:/\.(png|jpg|gif)$/,//打包文件是以png,jpg,gif结尾的正则匹配
                use:{
                    loader:'url-loader',
                    options:{
                        name:'[name].[ext]',//按原有名字打包
                        outputPath:'images/',//打包到dist下的miages文件夹里
                        limit:2048,//图片小于2kb时,图片会以base64存在,大于2kb则会打包成图片文件
                    }
                }
            },
            {
                test:/\.scss$/,//打包scss的loader配置
                use:[
                'style-loader',
                {
                    loader:'css-loader',
                    options:{
                        importLoaders:2//意思是当scss有引入关系时候,引入的scss文件重新走一遍这几项loader
                    }
                },
                'sass-loader',
                'postcss-loader'
                ]
            }
            ]
        }
    }
    

    2019/4/26
    学习了以上内容,我们来做一个练习,将阿里矢量图标打包到我们的项目之中。过程如下:
    先进入阿里矢量图官网,没有账号的要先注册,注册成功之后将需要用的图标下载到本地,解压缩之后将这几个文件复制然后粘贴到项目的font目录中

    文件.png
    然后我们修改配置文件如下:
    //引入一个node路径有关的核心模块,用于配置输出文件的路径
    const path=require('path');
    module.exports={
        mode:'production',//打包出的文件格式
        //入口文件,也就是webpack要打包的文件
        entry:'./js/index.js',
        output:{
            filename:'bundle.js',//打包后的文件名
            path:path.resolve(__dirname,'dist'),//打包后文件所在的路径,根目录的dist文件夹,__dirname代表项目根目录
        },
        module:{
            rules:[{
                test:/\.(png|jpg|gif)$/,//打包文件是以png,jpg,gif结尾的正则匹配
                use:{
                    loader:'url-loader',
                    options:{
                        name:'[name].[ext]',//按原有名字打包
                        outputPath:'images/',//打包到dist下的miages文件夹里
                        limit:2048,//图片小于2kb时,图片会以base64存在,大于2kb则会打包成图片文件
                    }
                }
            },
            {
                test: /\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/,//矢量图需要引入的这些类型的文件,所以要用file-loader打包
                loader: 'file-loader'           
            },
            {
                test: /\.css$/,//因为矢量图引入了css,所以需要这两个loader
                use: [
                  'style-loader',
                  'css-loader'
                ],
            },      
            {
                test:/\.scss$/,//打包scss的loader配置
                use:[
                'style-loader',
                {
                    loader:'css-loader',
                    options:{
                        importLoaders:2//意思是当scss有引入关系时候,引入的scss文件重新走一遍这几项loader
                    }
                },
                'sass-loader',
                'postcss-loader'
                ]
            }
            ]
        }
    }
    
    

    修改index.js代码如下:

    import '../font/iconfont.css'//引入矢量图标文件
    import '../css/index.scss'//引入css文件
    let A=require('./A.js')
    let imgSrc=require('../img/logo.png');
    let img=new Image();
    img.src=imgSrc;
    img.classList.add("image");//给img标签添加类名
    //在项目中添加矢量图
    document.getElementById('root').innerHTML='<div class="iconfont icon-shouji"></div>'
    document.getElementById('root').append(img)
    A();
    

    都修改好了之后就可以打包运行项目了,发现矢量图已经成功引入。

    plugins的使用

    之前我们打包的文件里的index.html文件是我们手动创建的html入口文件,如果能自动为我们创建这个入口文件那就方便了,那就要使用webpack的一个插件(html-webapck-plugin)为我们解决这个问题。
    首先在项目中安装这个插件,在命令行中输入:

    cnpm install html-webpack-plugin -D
    

    然后修改配置文件如下:

    //引入一个node路径有关的核心模块,用于配置输出文件的路径
    const path=require('path');
    //引入html-webpack-plugin插件
    const HtmlWebpackPlugin=require('html-webpack-plugin');
    module.exports={
        mode:'production',//打包出的文件格式
        //入口文件,也就是webpack要打包的文件
        entry:'./js/index.js',
        output:{
            filename:'bundle.js',//打包后的文件名
            path:path.resolve(__dirname,'dist'),//打包后文件所在的路径,根目录的dist文件夹,__dirname代表项目根目录
        },
        module:{
            rules:[{
                test:/\.(png|jpg|gif)$/,//打包文件是以png,jpg,gif结尾的正则匹配
                use:{
                    loader:'url-loader',
                    options:{
                        name:'[name].[ext]',//按原有名字打包
                        outputPath:'images/',//打包到dist下的miages文件夹里
                        limit:2048,//图片小于2kb时,图片会以base64存在,大于2kb则会打包成图片文件
                    }
                }
            },
            {
                test: /\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/,
                loader: 'file-loader'           
            },
            {
                test: /\.css$/,
                use: [
                  'style-loader',
                  'css-loader'
                ],
            },      
            {
                test:/\.scss$/,//打包scss的loader配置
                use:[
                'style-loader',
                {
                    loader:'css-loader',
                    options:{
                        importLoaders:2//意思是当scss有引入关系时候,引入的scss文件重新走一遍这几项loader
                    }
                },
                'sass-loader',
                'postcss-loader'
                ]
            }
            ]
        },
        plugins:[new HtmlWebpackPlugin()],//使用插件
    }
    

    删除之前打包生成的dist文件夹,然后重新打包,打包成功后,发现dist文件夹里已经为我们生成了index.html的入口文件,而且html里已经自动引入了我的打包的bundle.js文件。
    html-webpack-plugin这个插件会在打包结束后,自动生成一个html文件,并把打包生成的js自动引入到这个html文件中。
    我们运行这个index.html文件,发现页面是空的,什么都没有,这是因为我们之前的代码操作逻辑都进行再index.html的一个id名为root的一个根标签div中,但是插件为我们生成的index.html文件并没有这个根标签,如果我们想让插件也自动为我们创建这个根标签,那该怎么办呢,修改配置文件plugins如下:

    //引入一个node路径有关的核心模块,用于配置输出文件的路径
    const path=require('path');
    //引入html-webpack-plugin插件
    const HtmlWebpackPlugin=require('html-webpack-plugin');
    module.exports={
        mode:'production',//打包出的文件格式
        //入口文件,也就是webpack要打包的文件
        entry:'./js/index.js',
        output:{
            filename:'bundle.js',//打包后的文件名
            path:path.resolve(__dirname,'dist'),//打包后文件所在的路径,根目录的dist文件夹,__dirname代表项目根目录
        },
        module:{
            rules:[{
                test:/\.(png|jpg|gif)$/,//打包文件是以png,jpg,gif结尾的正则匹配
                use:{
                    loader:'url-loader',
                    options:{
                        name:'[name].[ext]',//按原有名字打包
                        outputPath:'images/',//打包到dist下的miages文件夹里
                        limit:2048,//图片小于2kb时,图片会以base64存在,大于2kb则会打包成图片文件
                    }
                }
            },
            {
                test: /\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/,
                loader: 'file-loader'           
            },
            {
                test: /\.css$/,
                use: [
                  'style-loader',
                  'css-loader'
                ],
            },      
            {
                test:/\.scss$/,//打包scss的loader配置
                use:[
                'style-loader',
                {
                    loader:'css-loader',
                    options:{
                        importLoaders:2//意思是当scss有引入关系时候,引入的scss文件重新走一遍这几项loader
                    }
                },
                'sass-loader',
                'postcss-loader'
                ]
            }
            ]
        },
        plugins:[new HtmlWebpackPlugin({
            template:'index.html'
        })],//使用插件
    }
    

    然后再项目根目录中创建一个index.html的模板,让插件能够根据模板自动创建打包后的index.html入口文件,代码如下:

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8" />
            <title>webpack-study</title>
        </head>
        <body id="root">
            <div id="root"></div>
        </body>
    </html>
    

    创建好了模板之后,我们打包文件,然后运行打包后的index.html文件,发现页面已经恢复正常了。
    plugin的本质就是当webpack运行到某个时刻的时候,帮你做一些事情,类似于vue中的生命周期函数。
    webpack每次打包过程中,并没有删除之前打包的文件,如果我们修改配置文件中的打包文件名,做如下修改:

        output:{
            filename:'dist.js',//打包后的文件名
            path:path.resolve(__dirname,'dist'),//打包后文件所在的路径,根目录的dist文件夹,__dirname代表项目根目录
        },
    

    然后再打包一次,会发现dist目录中同时存在着上次打包的bundle.js文件和本次打包的dist.js文件,那如果我们想让项目打包时删除之前打包的内容,就要借助另外一个插件clean-webpack-plugin,首先在命令行中安装这个插件

    cnpm install clean-webpack-plugin -D
    

    然后配置文件如下:

    //引入一个node路径有关的核心模块,用于配置输出文件的路径
    const path=require('path');
    //引入html-webpack-plugin插件
    const HtmlWebpackPlugin=require('html-webpack-plugin');
    const CleanWebpackPlugin=require('clean-webpack-plugin');
    module.exports={
        mode:'production',//打包出的文件格式
        //入口文件,也就是webpack要打包的文件
        entry:'./js/index.js',
        output:{
            filename:'bundle.js',//打包后的文件名
            path:path.resolve(__dirname,'dist'),//打包后文件所在的路径,根目录的dist文件夹,__dirname代表项目根目录
        },
        module:{
            rules:[{
                test:/\.(png|jpg|gif)$/,//打包文件是以png,jpg,gif结尾的正则匹配
                use:{
                    loader:'url-loader',
                    options:{
                        name:'[name].[ext]',//按原有名字打包
                        outputPath:'images/',//打包到dist下的miages文件夹里
                        limit:2048,//图片小于2kb时,图片会以base64存在,大于2kb则会打包成图片文件
                    }
                }
            },
            {
                test: /\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/,
                loader: 'file-loader'           
            },
            {
                test: /\.css$/,
                use: [
                  'style-loader',
                  'css-loader'
                ],
            },      
            {
                test:/\.scss$/,//打包scss的loader配置
                use:[
                'style-loader',
                {
                    loader:'css-loader',
                    options:{
                        importLoaders:2//意思是当scss有引入关系时候,引入的scss文件重新走一遍这几项loader
                    }
                },
                'sass-loader',
                'postcss-loader'
                ]
            }
            ]
        },
        plugins:[new HtmlWebpackPlugin({
            template:'index.html'
        }),new CleanWebpackPlugin()],//使用CleanWebpackPlugin插件删除dist目录下的所有内容
    }
    

    配置好了之后,我们再dist文件夹中新增一个a.js文件,然后打包这个项目,打包成功后,我们查看dist目录,发现a.js文件已经不存在了,说明这个插件确实为我们在打包之前删除了dist目录中的所有文件。
    2019/4/27

    entry与output的基本配置

    entry的配置可以使用字符串简写形式:entry:'./js/index.js',也可以使用键值对形式:entry:{main:'./js/index.js'},键值对形式打包生成的文件默认是main.js,如果配置了output输出文件的名字,则以output配置为主。现在我们想打包两次这个index.js文件(或者打包多个入口文件),我们修改entry的的配置项如下:

    entry:{main:'./js/index.js',bundle:'./js/index.js'}
    

    然后执行打包命令,发现报错了,这是因为要想打包多文件,输出文件的的名字不能重复,这时我们要修改output的配置项

        output:{
            filename:'[name].[ext]',//打包后的文件名
            path:path.resolve(__dirname,'dist'),//打包后文件所在的路径,根目录的dist文件夹,__dirname代表项目根目录
        },
    

    然后进行打包,webpack会为我们自动打包成两个文件main.js和bundle.js文件,然后再打开index.html,发现这两个打包文件也都被引入进来了。
    有些时候,我们需要把我们打包好的js文件放入的cdn中,然后再html入口文件中引入这个cdn的路径,这时就要配置output项了,如下:

        output:{
            publicPath:'http://cdn.com.cn',
            filename:'[name].[ext]',//打包后的文件名
            path:path.resolve(__dirname,'dist'),//打包后文件所在的路径,根目录的dist文件夹,__dirname代表项目根目录
        },
    

    然后我们重新打包,打包好了之后打开dist目录的index.html文件,发现引入的打包文件已经配置成了cdn路径。

    SourceMap的配置

    当我们编写代码出错的时候,浏览器控制台会提示我们错误的位置,sourcemap的作用是让浏览器告诉我们源代码错误的位置,而不是打包后js的错误代码位置,它是一种打包后文件和源文件的对应关系的映射,所以我们要对sourcemap进行配置,通过这种映射关系来找出源码错误的位置,配置如下:

    mode:'development',
    devtool:'source-map'
    

    当devtool的值为souce-map时,运行打包命令后,会在dist目录中为我们生成一个map映射文件。
    当devtool的值为inline-souce-map时,map文件会被直接写在main.js中,而不会以map文件形式存在。
    cheap-souce-map 报错内容只会提示到行,而没有列。
    最佳实践使用:cheap-module-eval-source-map(mode为development时)
    cheap-module-source-map(mode为prodution时)
    2019/4/28

    WebpackDevServer的使用

    之前的项目,每当我们修改源代码,都要进行一次打包才能更新程序,如果我们修改源代码之后,webpack能自动实现帮我们打包,那效率就会大大提升,有两种办法实现这种效果,下面就来具体介绍一下。
    第一种我们修改package.json文件,对scripts的值做如下修改:

      "scripts": {
        "bundle": "webpack",
        "watch": "webpack --watch"
      },
    

    然后在命令行中运行npm run watch,这回我们修改index.js文件,发现修改完毕保存后,webapck会自动的帮我们进行打包。watch的意思是webpack帮我们自动监听我们要打包的文件,当文件发生变化时,webpack会将这个文件自动进行打包。
    第二种做法可以在我们运行打包命令的时候,自动为我们打开浏览器,并执行html入口文件,同时还能够模拟服务器上的特性,这就要借助webapckdevserver。首先要在项目中安装webapck-dev-server,在命令行中输入:

    cnpm install webpack-dev-server -D
    

    安装好了之后,我们就要在配置文件中添加devServer属性,并进行配置,如下:

        devServer:{
            contentBase:'./dist',//意思是服务器要启动在哪个文件夹下
            open:true,//在运行打包项目时,会自动打开webpack本地服务器,并在服务器上运行我们打包后的代码 
            proxy:{
                '/api':'http://xxxxxxx'//如果用户访问api这个地址,会被转发到后面写的这个地址,这里只是简单介绍。具体使用要查阅官方文档。
            }
        },
    

    然后修改package.json文件中的scripts属性,如下:

      "scripts": {
        "bundle": "webpack",
        "watch": "webpack --watch",
        "start": "webpack-dev-server"
      },
    

    配置好了之后在命令行中输入npm run start,浏览器会自动启动,并运行打包后的项目。webpackdevserver并没有把文件打包到dist目录中,而是帮我们打包到了电脑的内存中,这样可以使打包的速度更快,并没有影响我们的使用。

    Hot Module Replacement 热模块更新

    我们在使用前面项目的配置文件时,运行项目,然后在页面上进行各种业务逻辑操作,发现有个地方的颜色需要改变,我们就去修改了css样式文件,当保存之后,我们又回到页面,发现页面刷新了,之前的业务逻辑操作的状态都不复存在了,那我们能否在改变代码的同时把之前的业务逻辑操作的状态也保存住,而不去刷新浏览器呢?这时我们就要借助webpack的Hot Module Replacement热模块更新功能。
    我们修改devServer属性:

        devServer:{
            contentBase:'./dist',//意思是服务器要启动在哪个文件夹下
            open:true,//在运行打包项目时,会自动打开webpack本地服务器,并在服务器上运行我们打包后的代码 
            hot:true,
            hotOnly:true,
        },
    

    在webpack.config.js文件中引入‘webpack’

    const webpack=require('webpack')
    

    修改plugins配置项:

        plugins:[new HtmlWebpackPlugin({
            template:'index.html'
        }),new CleanWebpackPlugin(),new webpack.HotModuleReplacementPlugin()],
    

    这些都配置好了之后,热模块更新功能就可以使用了,当我们在页面进行逻辑操作时,修改css样式时候,浏览器并没有重新刷新,之前进行的逻辑操作状态还在。
    2019/4/29

    使用 Babel 处理 ES6 语法

    当我们使用ES6语法进行代码编写的时候,有一些低版本的浏览器可能无法兼容ES6语法,所以我们就要借助webapck的babel帮我们将ES6转化成浏览器能够识别的语法,首先我们修改index.js代码,在里面写ES6语法,如下:

    //ES6语法
    const arr=[
        new Promise(()=>{}),
        new Promise(()=>{})
    ]
    arr.map(item=>{
        console.log(item)
    })
    

    然后我们在项目中安装babel-loader、@babel/core

    cnpm install --save-dev babel-loader @babel/core
    

    安装好了之后,就要在配置文件中的module增加这个loader的执行规则,如下:

    module: {
      rules: [
        { test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader' }
      ]
    }
    

    意思是当babel检查到.js结尾的文件时,会使用babel-loader将es6语法进行转换,exclude的意思是排除node_modules目录下的文件,因为node_modules目录中存放的是我们外部引入的库文件,所以不需要进行转化。
    之前我们安装的babel-loader并不能直接转换我们的代码,我们还要安装@babel/preset-env来进行彻底转化,在命令行中输入如下命令进行安装:

    cnpm install @babel/preset-env --save-dev
    

    安装好了之后再修改之前的配置规则:

    module: {
      rules: [
            { 
                test: /\.js$/, 
                exclude: /node_modules/, 
                loader: 'babel-loader' ,
                options:{
                    presets:['@babel/preset-env']
                }
            }
      ]
    }
    

    最后我们对项目进行打包,然后查看打包后的bundle.js代码,发现之前用const定义的变量已经转换成了var,这就说明我们成功将es6语法进行了转换。当然这种转换还远远不够,因为低版本的浏览器可能缺少很多es语法的引入,所以我们还要安装@babel/polyfill将这些es6语法导入浏览器,首先要在项目中安装:

    cnpm install --save @babel/polyfill
    

    然后我们在入口文件(index.js)的最顶部进行引入,添加如下代码:

    import "@babel/polyfill";
    

    这种引入会将所有的es6语法引入到浏览器中,体积量会相对较大,我们要进行优化,让它进行按需引入,也就是说我们在项目中使用了哪些es6语法,我们就相应的引入哪些语法。我们修改相应的配置帮我们实现按需引入(当配置了useBuiltIns为usage时,入口文件不用引入import "@babel/polyfill";),修改配置如下:

    module: {
      rules: [
            { 
                test: /\.js$/, 
                exclude: /node_modules/, 
                loader: 'babel-loader' ,
                options:{
                    presets:[['@babel/preset-env',{
                        useBuiltIns:'usage',                    
                    }]]
                }
            },
      ]
    }
    

    有些时候我们的浏览器版本已经很高了,就不需要进行es6转换,我们可以通过配置,根据浏览器的版本来进行是否转换es6语法,完整版配置如下:

    //引入一个node路径有关的核心模块,用于配置输出文件的路径
    const path=require('path');
    //引入html-webpack-plugin插件
    const HtmlWebpackPlugin=require('html-webpack-plugin');
    const CleanWebpackPlugin=require('clean-webpack-plugin');
    const webpack=require('webpack');
    module.exports={
        mode:'development',
        devtool:'cheap-module-eval-source-map',
        //入口文件,也就是webpack要打包的文件
        entry:'./js/index.js',
        output:{
            filename:'bundle.js',//打包后的文件名
            path:path.resolve(__dirname,'dist'),//打包后文件所在的路径,根目录的dist文件夹,__dirname代表项目根目录
        },
        devServer:{
            contentBase:'./dist',//意思是服务器要启动在哪个文件夹下
            open:true,//在运行打包项目时,会自动打开webpack本地服务器,并在服务器上运行我们打包后的代码 
            hot:true,
            hotOnly:true,
        },
        module:{
            rules:[{
                test:/\.(png|jpg|gif)$/,//打包文件是以png,jpg,gif结尾的正则匹配
                use:{
                    loader:'url-loader',
                    options:{
                        name:'[name].[ext]',//按原有名字打包
                        outputPath:'images/',//打包到dist下的miages文件夹里
                        limit:2048,//图片小于2kb时,图片会以base64存在,大于2kb则会打包成图片文件
                    }
                }
            },
            { 
                test: /\.js$/, 
                exclude: /node_modules/, 
                loader: 'babel-loader' ,
                options:{
                    presets:[['@babel/preset-env',{
                        useBuiltIns:'usage',
                        targets: {
                          edge: "17",
                          firefox: "60",
                          chrome: "67",
                          safari: "11.1",
                        },                  
                    }]]
                }
            },
            {
                test: /\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/,
                loader: 'file-loader'           
            },
            {
                test: /\.css$/,
                use: [
                  'style-loader',
                  'css-loader',
                  'postcss-loader'
                ],
            },      
            {
                test:/\.scss$/,//打包scss的loader配置
                use:[
                'style-loader',
                {
                    loader:'css-loader',
                    options:{
                        importLoaders:2//意思是当scss有引入关系时候,引入的scss文件重新走一遍这几项loader
                    }
                },
                'sass-loader',
                'postcss-loader'
                ]
            }
            ]
        },
        plugins:[
            new HtmlWebpackPlugin({
                template:'index.html'
            }),
            new CleanWebpackPlugin(),
            new webpack.HotModuleReplacementPlugin()
        ],//使用CleanWebpackPlugin插件删除dist目录下的所有内容
    }
    
    

    注意以上处理es6语法的方法不适用于所有场景,当我们做基础组件库开发的时候,就不适合在index.js入口文件中引入import "@babel/polyfill",因为这种引入会污染全局变量,这时我们就要换一种配置方式。
    修改入口文件index.js代码如下:

    //ES6语法
    const arr=[
        new Promise(()=>{}),
        new Promise(()=>{})
    ]
    arr.map(item=>{
        console.log(item)
    })
    

    在项目中安装

    cnpm install --save-dev @babel/plugin-transform-runtime
    cnpm install --save @babel/runtime
    cnpm install --save @babel/runtime-corejs2
    

    然后修改babel-loader的配置项如下:

            { 
                test: /\.js$/, 
                exclude: /node_modules/, 
                loader: 'babel-loader' ,
                options:{
                    plugins: [['@babel/plugin-transform-runtime',{
                        "corejs": 2,
                        "helpers": true,
                        "regenerator": true,
                        "useESModules": false               
                    }]]             
    //              presets:[['@babel/preset-env',{
    //                  useBuiltIns:'usage',
    //                  targets: {
    //                    edge: "17",
    //                    firefox: "60",
    //                    chrome: "67",
    //                    safari: "11.1",
    //                  },                  
    //              }]]
                }
            },
    

    配置好了之后就可以打包文件了。
    我们发现配置babel-loader的配置项代码很长,其实这里可以进行提取,我们在项目根目录中创建.babelrc文件,然后将babel-loader配置项的options中的对象直接复制过来,再将options配置整体删除。
    2019/4/30

    webpack4高级篇

    Tree Shaking 概念详解

    我们还是利用之前配置好的项目,在js文件夹下创建A.js文件,然后编写代码如下:

    export const add=(a,b)=>{
        console.log(a+b);
    }
    export const minus=(a,b)=>{
        console.log(a-b);
    }
    

    在index.js中使用A.js文件编写好的模块,代码如下:

    import {add} from "./A.js"
    add(1,2);
    

    这里我们只是使用了A.js中的add功能,并没有引入和使用minus功能,我们继续打包代码,在控制台运行npm run bundle,然后执行打包后的代码,发现程序可以按照预期正常执行,我们先打开打包后的bundle.js代码,通过编译器搜索console.log,发现我们没有引入和使用的minus功能也被打包进去了,一旦打包了我们定义好但是没有使用的模块,会使我们的打包文件体积变得非常庞大,这是我们不希望看到的,所以我们就要借助webpack的Tree Shaking功能帮我们解决这个问题。首先要注意的是Tree Shaking只支持ES模块方式的引入,我们打开之前配置好的配置文件,当mode为development时,默认是没有Tree Shaking功能,我们必须把它加上,在配置文件的plugins下面继续添加一个配置项,如下:

        optimization:{
            usedExports:true
        },
    

    然后打开package.json文件继续添加一个配置项如下:

    "sideEffects":false,
    

    意思是webpack只要遇到模块,就按照Tree Shaking的模式打包,但是,我们引入import '@babel/polly-fill',类似这种方式引入文件,而没有引入模块时,Tree Shaking发现没有导出模块,会自动忽略而不去进行打包,所以我们要配置package.json中的"sideEffects",把不需要进行Tree Shaking的文件配置进去(当然如果只有模块的引入而没有文件的引入情况下,是可以配置为false的),例如:

    "sideEffects":['@babel/polly-fill','*.css'],
    

    都配置好了之后,我们打包代码,然后查看打包后的bundle.js代码,发现模块的两个功能依旧都引入了,只不过代码中进行了说明和区分,这时因为开发模式中需要进行一些代码调试,如果删除某一部分功能,source-map对应的代码行数会有误。当我们把mode模式修改为production时,一些Tree Shaking的配置已经为我们自动写好了,所以我们就不需要optimization这个配置项了。

    Develoment 和 Production 模式的区分打包

    当我们进行代码开发时,一般mode配置为develoment,开发环境可以为我们提供一个带有HMR特性的本地服务器,当我们修改代码时候,会为我们自动打包文件,并实时更新浏览器的运行结果,所以开发的时候使用这种模式会非常方便。当项目开发完成要发布到线上时,一般mode配置为production,这种模式的sourcemap会很简略(不准),代码也是被压缩的,所以这两种配置是有区别的,如果我们要在开发模式和线上模式进行切换,就要反复的修改配置文件,这样会很不方便,所以我们就要进行两种模式的区分打包配置。我们在项目跟目中创建webpack.dev.js和webpack.prod.js分别代表开发环境的配置文件和生产环境的配置文件,再创建一个webpack.common.js代表开发环境和生产环境公共的配置文件,把它们共用的部分代码提取出来放到这里面。
    webpack.common.js配置如下:

    const path=require('path');
    const HtmlWebpackPlugin=require('html-webpack-plugin');
    const CleanWebpackPlugin=require('clean-webpack-plugin');
    module.exports={
        entry:'./js/index.js',
        output:{
            filename:'bundle.js',//打包后的文件名
            path:path.resolve(__dirname,'dist'),//打包后文件所在的路径,根目录的dist文件夹,__dirname代表项目根目录
        },  
        module:{
            rules:[{
                test:/\.(png|jpg|gif)$/,//打包文件是以png,jpg,gif结尾的正则匹配
                use:{
                    loader:'url-loader',
                    options:{
                        name:'[name].[ext]',//按原有名字打包
                        outputPath:'images/',//打包到dist下的miages文件夹里
                        limit:2048,//图片小于2kb时,图片会以base64存在,大于2kb则会打包成图片文件
                    }
                }
            },
            { 
                test: /\.js$/, 
                exclude: /node_modules/, 
                loader: 'babel-loader' ,
            },
            {
                test: /\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/,
                loader: 'file-loader'           
            },
            {
                test: /\.css$/,
                use: [
                  'style-loader',
                  'css-loader',
                  'postcss-loader'
                ],
            },      
            {
                test:/\.scss$/,//打包scss的loader配置
                use:[
                'style-loader',
                {
                    loader:'css-loader',
                    options:{
                        importLoaders:2//意思是当scss有引入关系时候,引入的scss文件重新走一遍这几项loader
                    }
                },
                'sass-loader',
                'postcss-loader'
                ]
            }
            ]
        },
        plugins:[
            new HtmlWebpackPlugin({
                template:'index.html'
            }),
            new CleanWebpackPlugin(),
        ],//使用CleanWebpackPlugin插件删除dist目录下的所有内容    
    }
    

    webpack.dev.js配置如下:

    //引入一个node路径有关的核心模块,用于配置输出文件的路径
    const path=require('path');
    const webpack=require('webpack');
    module.exports={
        mode:'development',
        devtool:'cheap-module-eval-source-map',
        devServer:{
            contentBase:'./dist',//意思是服务器要启动在哪个文件夹下
            open:true,//在运行打包项目时,会自动打开webpack本地服务器,并在服务器上运行我们打包后的代码 
            hot:true,
            //hotOnly:true,//当删除这个配置项时候,修改代码会自动刷新浏览器
        },
    
        plugins:[
            new webpack.HotModuleReplacementPlugin()
        ],//使用CleanWebpackPlugin插件删除dist目录下的所有内容
        optimization:{
            usedExports:true
        },
    }
    

    webpack.prod.js配置如下:

    module.exports={
        mode:'production',
        devtool:'cheap-module-source-map',
    }
    

    都配置好了之后,我们还要想办法把webpack.common.js中的配置合并到dev和prod配置中,需要安装webpack-merge模块来帮我们进行合并,在命令行中输入如下命令:

    cnpm install webpack-merge -D
    

    安装好了之后还要修改webpack.dev.js配置文件:

    //引入一个node路径有关的核心模块,用于配置输出文件的路径
    const path=require('path');
    const webpack=require('webpack');
    const merge=require("webpack-merge");
    const commonConfig=require('./webpack.common.js');
    
    
    const devConfig={
        mode:'development',
        devtool:'cheap-module-eval-source-map',
        devServer:{
            contentBase:'./dist',//意思是服务器要启动在哪个文件夹下
            open:true,//在运行打包项目时,会自动打开webpack本地服务器,并在服务器上运行我们打包后的代码 
            hot:true,
            //hotOnly:true,//当删除这个配置项时候,修改代码会自动刷新浏览器
        },
    
        plugins:[
            new webpack.HotModuleReplacementPlugin()
        ],//使用CleanWebpackPlugin插件删除dist目录下的所有内容
        optimization:{
            usedExports:true
        },
    }
    
    module.exports=merge(commonConfig,devConfig);
    
    

    再修改webpack.prod.js配置文件:

    const merge=require("webpack-merge");
    const commonConfig=require('./webpack.common.js');
    
    
    const prodConfig={
        mode:'production',
        devtool:'cheap-module-source-map',
    }
    
    module.exports=merge(commonConfig,prodConfig);
    
    

    都配置好了之后我们执行npm run dev,发现开发环境已经成功启用;我们关闭开发环境,然后运行npm run build,打包成功之后我们运行打包后的文件,发现程序能正常运行,这就说明我们已经成功配置了Develoment 和 Production 模式的区分打包。
    有些时候,我们需要把配置文件放到根目录中的build文件中,这时我们就要修改package.json文件中的scripts配置项如下:

      "scripts": {
        "bundle": "webpack",
        "watch": "webpack --watch",
        "dev": "webpack-dev-server --config ./build/webpack.dev.js",
        "build": "webpack --config ./build/webpack.prod.js"
      },
    

    2019/5/6
    当我们想运行开发环境的项目,并且希望能打包生成开发环境代码,我们就需要修改package.json文件的scripts配置项如下:

      "scripts": {
        "bundle": "webpack",
        "watch": "webpack --watch",
        "dev": "webpack-dev-server --config ./build/webpack.dev.js",
        "build": "webpack --config ./build/webpack.prod.js",
        "dev-build":"webpack --config ./build/webpack.dev.js"
      },
    

    我们先删除根目录中的dist文件夹,然后在命令行中运行

    npm run dev-build
    

    发现跟目中并没有生成dist文件夹,我们打开build文件夹,发现打包生成的代码跑到这里来了,所以我们就要修改webpack.common.js文件中的output项来改变打包文件的路径,修改后的配置如下:

        output:{
            filename:'bundle.js',//打包后的文件名
            path:path.resolve(__dirname,'../dist'),//打包后文件所在的路径,根目录的dist文件夹,__dirname代表项目根目录
        },
    

    Code Splitting的介绍

    有些时候,我们在编写业务逻辑层代码时,要引入第三方工具库,然后在业务层使用第三方工具库,这些处理代码并不会影响正常功能的使用,但是一旦工具库的体积非常大,打包之后的业务层代码也会变得特别大,当我们修改业务层代码时,就会重新加载一遍业务层和被引入的第三方库代码,这样就会影响效率,所以我们就要把业务层进行拆分,将引入第三方库拆为一层,再将业务部分拆分为一层,这样当我们修改业务代码时,就不会重新加载第三方库的那一部分,这样效率就会有所提高,这也是在某些特定的情况下我们要进行Code Splitting的一个主要原因。webpack中的一些插件可以帮我们自动处理Code Splitting,我们需要配置webpack.common.js,添加一个配置项如下:

        optimization:{
            splitChunks:{
                chunks:'all',//允许同步和异步分割打包
                minSize:3000,//当模块文件大于30kb时,进行分割打包,否则正常处理
                minChunks:1,//意思是引入多少次才进行代码分割
                cacheGroups:{
                    vendors:{
                        test:/[\\/]node_modules[\\/]/,//当库文件在node_modules文件夹中才会进行打包
                        priority:-10,//优先级与default的优先级比较
                        filename:'vendors.js',//分割打包后的文件名,所有分割打包的文件都打包到这里
                    },
                    default:{//当库文件不在node_modules里时,打包到common.js中
                        priority:-20,
                        reuseExistingChunk:true,//自动判断库文件之间的引用关系,减少重复打包
                        filename:'common.js'
                    }
                }
            } 
        }
    

    这样webpack就能自动帮我们处理代码分割了。有些时候,我们为了提高代码效率,会使用到异步模块引入,这时我们需要进行如下安装和配置,命令行安装:

    cnpm install --save-dev @babel/plugin-syntax-dynamic-import
    

    配置 .babelrc文件如下:

    {
                
                    presets:[['@babel/preset-env',{
                        useBuiltIns:'usage',
                        targets: {
                          edge: "17",
                          firefox: "60",
                          chrome: "67",
                          safari: "11.1",
                        },                  
                    }]],
                    plugins:["@babel/plugin-syntax-dynamic-import"]
    }
    

    例如我们要异步引入lodash模块,先安装lodash

    cnpm install lodash --save
    

    然后编写index.js文件代码如下:

    import _ from 'lodash'
    async function getComponent(){
        const {default: _ }=await import('lodash')
        var element=document.createElement('div');
        element.innerHTML=_.join(["Sean","liuyang"],"-")
        return element;
    
    }
    getComponent().then((element)=>{
        document.body.appendChild(element);
    })
    

    代码编写好了就可以打包运行代码了,发现dist目录中已经把lodash工具库分割到了vendors.js文件中了。

    利用Prefetching来提高项目的代码覆盖率

    浏览器中自带的缓存功能可以为我们加快第二次浏览时的加载速度,但是第一次加载的速度明显会比第二次慢很多,如果想提高第一次网页的加载速度,就要提高代码的覆盖率(谷歌浏览器调出控制台,ctrl+shift+p ,输入coverage回车就会看到项目代码的覆盖率),我们可以使用懒加载一些代码块来提高首次访问页面的速度(提高首页的代码覆盖率),比如一个网页中有一个按钮,点击这个按钮会出现一个弹窗,我们可以把这个弹窗模块进行懒加载来提高首次访问页面的速度,也就是当鼠标点击按钮的时候,该弹窗模块才会引入并加载进来,但是如果这个弹窗模块业务逻辑非常复杂,代码量非常大,那么点击这个弹窗按钮后,弹窗模块的渲染速度就会很慢,非常影响用户体验,如果能在首页面加载完毕之后,利用空闲的带宽偷偷的加载这个弹窗模块,那页面代码覆盖率就会非常高,我们可以借助webpack中的Prefetching功能来帮我们实现这种效果。来看如下demo,编写index.js代码如下:

    document.addEventListener('click',()=>{
        let element=document.createElement('div');
        element.innerHTML="Sean Liu";
        document.body.appendChild(element);
    })
    

    效果是当点击浏览器页面,会渲染一条Sean Liu这条数据,然后通过ctrl+shift+p查看代码覆盖率,记住这个覆盖率。我们可以对代码进行优化,如下:
    然后新建一个click.js文件,代码如下:

    function handleClick(){
        let element=document.createElement('div');
        element.innerHTML="Sean Liu";
        document.body.appendChild(element);
    
    }
    export default handleClick;
    

    修改index.js代码如下:

    document.addEventListener('click',()=>{
        import('./click.js').then(({default:fn})=>{
            fn();
        })
    })
    

    然后打包代码,打包完成后运行,查看代码覆盖率,发现这种懒加载的优化方案代码覆盖率确实提高了。来看下一种利用Prefetching功能来减少首页加载时间,利用空闲带宽偷偷加载,只要修改index.js代码就行了,如下:

    document.addEventListener('click',()=>{
        import(/* webpackPrefetch: true */'./click.js').then(({default:fn})=>{
            fn();
        })
    })
    

    意思是当点击页面按钮时会引入模块代码,但不是非得点击按钮这个模块才会被引入,当页面带宽空闲的时候,也会为我们引入该模块。通过这种魔鬼注释语法来使用Prefetching功能提高代码覆盖率。

    使用webpack解决线上环境缓存问题

    当我们把代码打包成功后放到服务器线上环境时,发现某一个文件有bug,需要进行修复,我们就把这个文件修复好了之后重新打包放到服务器上,但是发现和之前的页面一样,bug并没有修复,这是因为浏览器的缓存机制,它会复用之前引入的文件,当我们手动清除浏览器缓存,然后刷新页面之后,这时的页面才会显示修复bug之后的效果,但是如何做到不手动清除浏览器缓存的情况下显示最新修改的页面呢?这时就要修改我们的webpack配置文件中的output项,如下:
    webpack.common.js

        output:{
            path:path.resolve(__dirname,'../dist'),//打包后文件所在的路径,根目录的dist文件夹,__dirname代表项目根目录
        },
    
    

    webpack.dev.js

        output:{
            filename:'bundle.js',//打包后的文件名
            chunkFilename:'[name].js',
        },
    

    webpack.prod.js

        output:{
            filename:'bundle.[contenthash].js',//打包后的文件名
            chunkFilename:'[name].[contenthash].js',
        },
    

    webpack.prod.js代码的意思是,打包成功后的文件会带有hash后缀,一旦内容发生变化,hash后缀也就会发生变化,这样浏览器就会重新加载改变后的文件,就不会出现缓存的效果了。
    2019/5/17

    Shimming

    当我们想让ES6语法在低版本浏览器运行时,通常会在浏览器中通过注入支持ES6的新语法来帮我们实现这一部分的兼容性问题,之前我们是利用polyfill来解决这一部分的兼容性问题的,这就是Shimming功能的一种,当然它还会有很多功能。当我们使用第三方工具库时,通常会将第三方库在js入口文件引入(index.js最为全局的引入),然后在各个模块中使用第三方库,就来之前安装好的lodash来说,index.js代码编写如下:

    import _ from 'lodash'
    import A from './A.js'
    A();
    

    编写A.js代码如下:

    export default function A(){
        alert(_.join(["Sean2222","liuyang"],"-"))
    }
    

    其实这种代码是不可能被正确的执行的,因为每个模块都有它自己的作用域,lodash是被引入到入口模块的,A.js模块是不可能访问到index.js模块中的内容的,因为他们的作用域都是相对独立的,如果想让被引入到人口文件的第三方库能在各个功能模块中正常使用,就要在各个模块中也引入第三方库,但这种做法太麻烦,所以我们就要借助shimming来解决这种问题,我们修改webpack.common.js文件,在代码中引入webpack模块,并添加plugins项功能,代码如下:

    const webpack=require('webpack');
        plugins:[
            new HtmlWebpackPlugin({
                template:'index.html'
            }),
            new CleanWebpackPlugin(),
            new webpack.ProvidePlugin({
                _:'lodash'
            })
        ],
    

    新增的new webpack.ProvidePlugin配置的意思是当我们在模块中使用“_”时,会在模块中自动引入lodash库,配置好了之后我们打包代码,发现程序可以正确执行。Shimming其实还有很多配置功能,这里只介绍最为常用的,其他的使用方式可以通过查阅文档来解决。

    结束语

    以上是webpack中最为核心的内容介绍,其它不常用的内容可以通过文档或者在网上搜索相关资料来查阅。我本人也是一个webpack的学习者,不敢说自己有多高的水平,虽然我尽最大努力,并在原稿上多次进行了修正,但不如人意之处恐怕还是不少,希望大家能多批评指正,或者通过留言的方式与我交流,我会在下一个版本中修正一些不足之处。

    止水
    于沈阳
    2019/05/20 06:44

    相关文章

      网友评论

          本文标题:webpack4从入门到掌握核心内容

          本文链接:https://www.haomeiwen.com/subject/rzqrgqtx.html