阅读 1395

【你应该掌握的】webpack4介绍&配置项详解

skFeTeam  本文作者:李键

引言

作为一名前端开发,按时按质实现业务需求只是基础条件,了解一些webpack相关的配置,除了能够帮助我们提升一下自己的技术能力(不局限于满足业务需求),也能够帮助我们更好的维护项目、搭建适合自己团队的web站点。

希望通过本文能让大家对webpack4的相关配置项有一个直观的了解,以例子的形式帮助大家更好&更快的掌握相关知识点。

webpack4介绍

  • webpack是一个JS应用打包器,它将应用中的各个模块打成一个或者多个bundle文件。借助loaders和plugins,它可以改变、压缩和优化各种各样的文件。它的输入是不同的资源,比如:js、css、图片、字体和html文件等等,然后将它们输出成浏览器可以正常解析的文件。
  • 2018.8.25,正式发布webpack4,相比于webpack3,带来很多的新特性更新和改善,让webpack配置更加简单
  • webppack4是目前比较主流打包工具之一,跟其他的打包工具相比,比如gulp,rollup等,webpack4优势:社区活跃度高,使用的人多,官方更新迭代快速。

webpack4配置项

准备工作

  • 安装node,webpack,然后新建文件夹–npm初始化
//webpack安装一般是不-g全局安装的,因为同时会进行好几个项目,有时候这几个项目的webpack版本都是不一样的,因此都是根据项目来局部安装,只在独立的项目中有效。
npm install webpack-cli webpack --save-dev 
//可以一路回车,初始化(项目名字,版本号,描述等默认就好,需要更改的可以在package.json文件中更改)
npm init/npm init -y 
复制代码

mode属性

  • mode属性可以设置为:development(代码不压缩)/production(代码压缩),在开发模式的时候可以提供更加详细的错误日志和提示,在生产环境打包的时候可以webpack专注项目的打包,去除掉开发阶段的运行的代码,是打包的生产环境的包文件更小。

entry/output

  • 入口和打包成功的出口–基本配置
//webpack打包的时候会默认找webpack.config.js文件
//使用path,首先需要引入
const path = require('path')
module.exports = {
    mode: 'development',//开发模式
    entry: './index.js',//指定入口文件
    output: {
        filename: 'bundle.js',//给成功打包的文件定义名字
        path: path.resolve(__dirname,'dist'),//__dirname--相对webpack.config.js这个文件同级根目录,生成的打包文件dist目录下面
    }
}
entry: {
    main: './src/index',
    sub: './src/index'
}
output: {
    publicPath: ''---如果需要加域名的话可以这样配置
    filename: '[name].js',---name占位符对应着entry中的key值
    path: resolve.path(__dirname,'dist')
}
复制代码
  • 代码演示—1
npx webpack -v
npx webpack index.js
//在package.js的script可以配置运行打包命令
"script": {
    "build": "webpack"
}
npm run build
复制代码
  • 代码运行成功后–package.json中的默认配置,npm webpack index.js成功后生成后,Hash–唯一的hash值,version–webpack版本,Time–打包耗费的时间,built中,asset–打包后的文件,size–打包文件的大小,chunks–每个打包文件对应的id值,chunk Names–打包文件名,main–对应着打包入口文件

loader

  • 什么是loader,webpack对不是js后缀的文件是不能进行处理的,这时候需要借助loader,loader其实就是一个打包方案,webpack对不是js后缀的文件不知道怎么去打包,但是loader是知道的,所以这时候webpack就要依赖loader来执行打包文件。
  • file-loader 图片/字体文件打包loader,这时候需要在webpack.config.js中增加图片/字体打包规则
//webpack.config.js
module.exports = {
    ...
    module: {
    //规则数组类型--有很多不同类型的打包规则
        rules: [
            {
                test: /\.(jpg|png|jpeg)$/,
                use: {
                    loader: 'file-loader',
                    options: {
                    //placehold--占位符 name--文件名,hash--hash值,ext-文件后缀名
                        name: '[name]_[hash].[ext]',
                        //回把图片打包到dist目录下image文件夹里
                        outputPath: "./image"
                    }
                }
            },{
                test:/\.(eot|ttf|svg)$/,
                use: {
                    loader: 'file-loader',
                    ...
                }
            }
        ]
    }
}
复制代码
  • url-loader 其包含了file-loader的图片打包的功能,区别:就是不加任何配置项的时候,默认把图片打包成base64字符放在打包的js文件中,优势,加载js文件完成之后图片也加载好了,减少一次http请求,不好的地方就是当图片文件过大的时候,那么被放入图片的打包js文件也会很大,那么在加载技js文件的时候,加载耗时较长,也面回停留空白页的时间过长,因此增加配置项limit,
rules: [
  {
      test:/\.(jpg|jepg|png)$/,
      use: {
          loader: 'url-loader',
          options: {
              filename: '[name]_[hash].[ext]',
              outputPath: './image',
              limit: 20480, //==20480个字节==20kb
              //大于20kb就会打包成图片放在dist目录下面,小于则打包成base64字符串放在打包的js文件里面
          }
      }
  }
]
复制代码
  • 代码演示–2 –使用url前后加了limit配置之后的对比
//使用file-loader ,url-loader 需要安装
npm install file-loader --save-dev
npm install url-loader --save-dev
复制代码

img
img
img
img

style-loader css-loader sass-loader postcss-loader

  • 同样的对css,scss打包的时候,webpack都是不知道怎么去打包,所以需要借助相关的loader来执行打包,要同时安装css-loader,style-loader,css-loader只负责处理css文件,在得到css-loader处理并且合并之后的css内容文件时,需要使用style-loader挂在到页面header部分,这样样式的打包才会在页面生效,同样的如果sass文件时,还需要安装sass-loader,node-sass ,因为sass-loader是依赖于node-sass来执行解析sass文件的,所以执行顺序是,先是sass-loader将scss文件解析成css文件,其次css-loader处理这些被解析成的css文件,然后再有style-loader将这些内容挂在到页面的header部分,postcss-loader就是增加厂商前缀的,是最先执行的。
//安装loader
npm install style-loader css-loader sass-loader node-sass postcss-loader --save-dev
//在使用postcss-loader的时候需要借助插件
//autoprefixer
npm install autoprefixer -D
//使用postcss-loader还需要增加配置文件
//postcss.config.js
module.exports = {
    plugins: [
        require('autoprefixer')
    ]
}
//webpack.config.js
module: {
    rules: [
        {
            test: /\.css$/,
            use: [
                "style-loader",
                <!--"css-loader",-->
                {
                    loader: "css-loader",
                    options: {
                        //表示css文件中又引入css文件,这样配置项可以使每一层都可以走到(下-上,右-左)
                        importLoader: 2,
                        //配置支持css模块化,避免样式渲染全局,name.类名的方式调用
                        <!--modules: true-->
                    }
                }
                "postcss-loader"
            ]
        },{
            test: /\.scss$/,
            use:[
                  "style-loader",
                <!--"css-loader",-->
                {
                    loader: "css-loader",
                    options: {
                        importLoader: 2,
                        <!--modules: true-->
                    }
                },
                "sass-loader",
                "postcss-loader"
            ]
        }
    ]
}
复制代码
  • 代码演示–3

img

img

img

img

img

img

img

img

plugins

  • html-webpack-plugin: 作用就是帮我们自动生成一个html,并且把打包好生成的js文件引入到html中,同时可以在src目录下创建一个html模板,然后可以设置配置项,根据这个html模板自动生成html放在dist目录下,,在打包完成之后开始执行
  • clean-webpack-plugin:作用就是把之前打包的dist目录删除掉,这个是在打包之前开始执行的
//首先安装html-webpack-plugin hlean-webpack-plugin
npm install html-webpack-plugin clean-webpack-plugin -D

//在webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html'
        }),
        new CleanWebpackPlugin('./dist')
    ]
}
复制代码
  • 代码演示 – 4 TypeError: CleanWebpackPlugin is not a constructor

img

img
img

//所以引用这个插件必须要使用对象结构
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
//这个插件使用默认配置就好了,如果要传入参数的话,必须以对象的形式
new CleanWebpackPlugin()/ new CleanWebpackPlugin({})
复制代码

img

sourceMap的配置

  • 这个是什么呢?,这个配置就是帮我们做一个打包之后和源代码之间的一个映射关系,当打包的时候出现问题的时候,我们并不是要知道打包文件哪一行出现错误,而是要知道源代码的哪一行出现错误。
//webpack.config.js
module.exports = {
    //基本配置
    devtool: "source-map"
    //可以提示比较全面的错误信息,具体到哪一行哪一列第几个字符,并且映射文件会打包到打包文件里面
    devtool: "inline-source-map"
    //开发环境配置推荐
    devtool: "cheap-module-eval-source-map"
    //生产环境
    devtool: "cheap-module-source-map"
}
复制代码

webpackDevServer

  • 可以帮我们监听文件的改动,还可以模拟服务器的一些特性,一旦src目录下的内容发生改变就会自动帮我们打包文件并且自动刷新页面,
//先安装webpack-dev-server
npm install webpack-dev-server -D

//package.json
"scripts": {
    "watch": "webpack --watch",
    "start": "webpack-dev-server",
}
    
//webpack.config.js
devServer: {
    contentBase: './dist',//本地服务器加载的目录
    open: true,//打包完成之后自动帮我们启动一个本地服务器,端口默认是8080,
    port: "",//端口可以自定义
    ...
}
复制代码

Hot Module Replace–HMR:热更新模块

  • 使用webpack-dev-server启动项目,帮我们启动一个本地服务器,并且还会自动帮我们打包,打包文件是放在内存当中,这样打包的速度更快,性能更好。
  • webpack自带的一个热更新模块插件 –HotModuleReplacementPlugin,但是作用于css和js的过程中,相交于css热更新,js需要多一下代码,原因是css-loader已经帮我们写好了,像vue框架热更新也是一样,已经内置已经写好的代码,react框架也是借助babel的preset的配置来处理的
// weebpack.config.js
const webpack = require('webpack');

module.exports = {
    devServer: {
        contentBase: './dist',
        port: 8080,
        open: true,
        hot: true,//--开启热更新功能
        hotOnly: true,//阻止页面自动刷新,即使hot更新功能不起作用,浏览器也不自动刷新页面
    }
    plugins: [
        new webpack.HotModuleReplacementPlugin()
    ]
}
//js
if(module.hot) {
    module.hot.accept('./number',() => {
        document.body.removeChild(document.getElementById('id'))
        number()//重新执行一下这个模块的函数
    })
}
复制代码
  • 代码演示–5
  • sourceMap
    img
    img
    img
    img
    img

babel处理es6语法

  • 对于普通浏览器目前是不支持es6语法的,但是支持es5语法代码,因此需要借助babel插件来做转换,这里可以查Babel的官方文档—这里省略一万步—这里面有使用场景的选择–选择webpack使用场景
//安装插件 --@babel/core是babel核心库
npm install babel-loader @babel/core -D
//同时要按装,babel-loader只是打通webpack的一个桥梁,并不会转义代码,需要借助@babel/preset-env 来做语法的转换
npm install @babel/preset-env -D
//还有继续安装@babel/polyfill -- 作用是帮助低版本的浏览器弥补缺失的变量以及函数,同时可以在options配置,根据业务代码来做低版本的缺失弥补,这样打包代码可以做到精简,注意的是,这个插件只适合做业务代码带包,因为会污染全局
npm install @babel/polyfill -D
//新建.babelrc文件,options配置可以放在这个文件里面
{
    presets: [
        [
            "@babel/preset-env",
            {
                "targets": {
                    "chrome": "67"
                },
                "useBuiltIns": "usage"// 按需加载
            }
        ]
    ]
}
//webpack.config.js
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
<!--options: {-->
<!--    presets: [['@babel/preset-env',{targets: {chrome: "67",},useBuiltIns: 'usage'}]]-->
<!--}-->
//test code --es6
import '@babel/polyfill'
const arr = [
new Promise(() => {}),
new Promise(() => {})
]
arr.map(item => {
    console.log(item)
})
复制代码

webpack实现对react框架代码的打包

  • 需要借助@babel/preset-react插件,在转换react框架代码之后还是要插件@babel/preset-env转换es6语法。
//使用安装插件
npm install react react-dom --save-dev
npm install @babel/preset-react --save-dev
//.babelrc中去引用插件
presets:[
    "@babel/preset-react"
]
//js--test-code
import React ,{ Component } from 'react'
import ReactDom from 'react-dom'
class App extends Compnent {
    render() {
        return(<div>herllo world</div>)
    }
}
ReactDom.render(<App/>,document.getElementById('root'));
复制代码

Tree shaking

  • 作用:这是webpack自带的一个功能,当引入一个模块的时候,不需要引入全部的代码,只需要引入被需要的代码,–就是摇树的意思,把不必要的叶子全部摇掉。
  • 但是webpack中的tree shaking仅支持esmodule这种引入方式。
//webpack.config.js
module.exports = {
    plugins: [],
    optimization: {
        usedExports:true//只是在开发环境需要配置,在生产环境把不需要配置
    }

}
//package.json中 开发环境是会保留这段代码的
"sideEffects": ['*.css']---忽略相关模块不做tree shaking
复制代码

development和production模式的区分打包,webpack.dev.js,webpack.pord.js,webpack.common.js –可以把文件放到build文件中

//需要下载 webpack-merge
npm install webpack-merge -D
//dev
const webpack = require('webpack');
const devConfig = {
    mode: 'development',
    devtool: 'cheap-module-source-map',
    devServer: {
        contentBase: '../dist',//本地服务器加载的目录
        open: true, //打包完成之后自动打开浏览器
        // port: 8080,
        hot: true,//开启模块热更新更能
        hotOnly: true,//阻止页面刷新,即使热更新功能失效,也不会刷新页面
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin()
    ], 
    optimization: {
        usedExports:true,
    },
}
module.exports = devConfig;
//pord
const pordConfig = {
mode: 'production',//production 
}
module.exports = pordConfig;
//common
const path = require('path');
const merge = require('webpack-merge')
const devConfig = require('./webpack.dev.js');
const prodConfig = require('./webpack.prod.js');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const commonConfig = {
    entry: {
        main: './src/index.js'
    },
    module: {
        //规则数组类型--有很多不同类型的打包规则
        rules: [
            // {
            //     test: /\.(jpg|png|jpeg)$/,
            //     use: {
            //         loader: 'file-loader',
            //         options: {
            //         //placehold--占位符 name--文件名,hash--hash值,ext-文件后缀名
            //             name: '[name]_[hash].[ext]',
            //             //回把图片打包到dist目录下image文件夹里
            //             outputPath: "./image"
            //         }
            //     }
            // },
            {
                test: /\.js$/,
                exclude:/node_modules/,
                loader: 'babel-loader'
            },
            {
                test: /\.(jpg|png|jpeg)$/,
                use: {
                    loader: 'url-loader',
                    options: {
                    //placehold--占位符 name--文件名,hash--hash值,ext-文件后缀名
                        name: '[name]_[hash].[ext]',
                        //回把图片打包到dist目录下image文件夹里
                        outputPath: "./image",
                        limit: 204800,
                    },
                }
            },{
                test:/\.(eot|ttf|svg)$/,
                use: {
                    loader: 'file-loader',
                }
            },{
                test: /\.css$/,
                use: [
                    "style-loader",
                    "css-loader",
                    "postcss-loader"
                ]
            },{
                test: /\.scss$/,
                use: [
                    "style-loader",
                    "css-loader",
                    "sass-loader",
                    "postcss-loader"
                ]
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: './src/index.html'
        }),
        // * All files inside webpack's output.path directory will be removed once, but the
        // * directory itself will not be. If using webpack 4+'s default configuration,
        // * everything under <PROJECT_DIR>/dist/ will be removed.
        new CleanWebpackPlugin({ verbose: true,cleanOnceBeforeBuildPatterns:['**/*']}),
    ],
    output: {
        filename: 'bundle.js',//打包成功后的文件名
        path: path.resolve(__dirname,'../dist'),//__dirname---表示跟web.config.js的同级根目录,打包之后的文件夹
    }
}

module.exports = (env) =>  {
    if(env&&env.production) {
        return merge(commonConfig,prodConfig)
    }else{
        return merge(commonConfig, devConfig)
    }
}
//package.js
"script": {
    "start": "webpack-dev-server --config ./build/webpack.common.js",
    "build": "webpack --env.production --config ./build/webpack.common.js",
    "dev": "webpack --env.development --config ./build/webpack.common.js"
}
复制代码
  • 代码演示 -6
    img
    img
    img

webpack和code Splitting:

  • 代码分割是webpack相当重要的一个特性,可以让代码分割到不同的文件中,一边按需加载或者并行加载这些文件,这样可以优化加载性能,以及用户体验会更好。
  • 代码分割其实只是在webpack中去实现代码分割,两种方式,同步代码分割只需optimization中配置即可,异步代码分割,webpack代码分割默认配置就是对异步代码进行代码分割,但是需要babel插件来做翻译,浏览器对异步的这种语法规则不支持。
//异步代码分割,安装babel插件,babel-plugin-dynamic-import-webpack
npm install babel-plugin-dynamic-import-webpack -D
//同步代码分割的话需要在optimization中配置splitChunks
//---在.babelrc中去配置一个plugins,
presets: [], 
plugins: ["dynamic-import-webpack"]
//webpack.common.js
optimization: {
    splitChunks: {
        chunks: 'all'//对同步代码和异步代码同时做代码分割
        chunks:'aysnc'//对异步代码做分割
    }
}
//test-code--index.js
function getComponent() { 
    return import('lodash').then(({default:_}) => {
        var element = document.createElement('div');
        element.innerHTML = _.join(['dell','lee'],'_');
        return element
    })
}
getComponent().then(element => {
    document.body.appendChild(element);
})
复制代码
  • splitChunksPlugin的底层配置中,异步代码会自动打包成一个文件0.js,这里需要指定引入异步代码文件的名字可以使用魔法注释,这里就需要这个插件@babel/plugin-syntax-dynamic-import来做异步代码的转化,不能使用上面那个插件babel-plugin-dynamic-import-webpack,/webpackChuunkName:‘lodash’/,
  • chunks:aysnc这种异步代码可以解决打包文件过大,加载时间过长,分出第三方库和插件,需要的时候在进行按需加载和并行加载,提供加载性能。
npm install @babel/plugin-syntax-dynamic-import --save-dev
splitChunks: {
    chunks: async //--如果是async的话,只是对异步代码做代码分割,all是对异步和同步分都做代码分割
    minSize: 30000===30kb //大于30kb就会做代码分割,小于的话就做代码分割
    maxSize:0//可配可不配 --如果是50000===50kb, 会做二次分割 lodash 打包成1mb ,会拆成20个50kb代码分割
    minChunks: 1,//当这个模块使用几次的话在做代码分割,小于的设置的次数就不做代码分割
    maxAsyncRequests: 5,//同时加载的模块是5个,在打包前五个会帮你打包做代码分割,超过五个的话就不做代码分割
    maxInitialRequests: 3,//指整个网站首页进行加载的时候或者是入口文件进行加载的时候,入口文件会引入其他库,也只能最多三个,超过三个就不会做代码分割了
    automaticNameDelimiter: '~',//文件生成的时候,文件的中间会有一些连接符,
    name: true, ---//起什么名字,让cacheGroups中的名字有效
    cacheGroups: {//如果是同步的话会走完chunks之后会走这个配置,缓存组
        vendors: {
            test: /[\\/]node_modules[\\/]/,//--是同步代码发现是从node_modules引入的话,那么符合这个组会被打包成单独文件
            priority: -10
            filename: 'vendors.js'//是打包文件的名字
        }
        default: {
            priority: -20,//假设同时符合两个组,通过这个配置来设置优先级,值越大,优先级越高
            reuseExistingChunk: true,如果一个模块a,b,如果a使用了b,符合代码分割的要求,然后又符合default这个组,就不打包之前打包过的内容
            filename: 'common.js'
        }
    }
}
复制代码
  • lazy loading—就是支持esmodule这种语法,按需加载。
  • css代码分割: filename与chunkFilename区别:官网的代码分割插件:做的事情就是把css文件单独打包,不直接打包到js文件里面,这个插件不支持HMR,所以css代码分割插件一般应用到生产环境.
//安装插件
npm install mini-css-extract-plugin --save-dev
//线上环境单独生成的css文件需要做代码压缩合并
npm install optimize-css-assets-webpack-plugin -D
//optimization
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require(' optimize-css-assets-webpack-plugin')
//配置做更改
//开发环境使用规则默认
//线上环境
module: {
    rules: [
            use: ['MiniCssExtractPlugin.loader'] //所有使用style-loader全部替换,不使用style-loader   
        ]
},
optimization: {minimizer: [new OptimizeCssAssetsWebpackPlugin({})]},
plugins: [new MiniCssExtractPlugin({
    filename: '[name].css',//被文件直接引用走这个配置
    chunkFilename: '[name].chunk.css'//被间接引用的话是走这个配置项
})]
output: {
    filename: '[name].js',
    chunkFilename: '[name].chunk.js'//就是被js间接引用的打包文件就会走这个配置内容,
    path: path.resolve(__dirname,'../dist')
}
复制代码

希望本文对大家有帮助。

想了解skFeTeam更多的分享文章,可以点这里,谢谢~