没了脚手架,怎么用webpack!?

628 阅读7分钟

1. 什么是webpack?

webpack 是一个现代 JavaScript 应用程序的静态模块打包器(static module bundler),简单来说,是一个前端模块化打包编译工具,基本的功能包括代码转换,文件优化,代码分割,自动刷新等。  

2. 如何运用webpack

webpack配置文件是基于node.js,并且遵循common.js来开发,
通常我们会在根目录下新建一个webpack.config.js文件,基本的结构目录如下图所示:

2.1 配置

初始化:webpack3中,webpack和cli是在同一个包中,webpack4中已经分开。 yarn add webpack webpack-cli

  • entry 入口
entry: {
    main: './src/index.js' //单入口
}
 entry: {
        main: './src/index.js', //多入口
        test: './src/test.js'
    }, //提示webpack把哪个文件作为构建文件的入口
output: {
    path: path.resolve(__dirname,'dist'),//必须是绝对路径
    //filename: 'bundle.js'
    filename:'[name].[hash:8].js'//打包多个文件,各自名字,并且加上md5戳,8位
}, //构建结束输出的位置

多入口打包出js。

打包后的js文件,名字bundle.js,如果想运行这个js,但是不能每次打包后都自己手动添加一个html,因此需要一个自动打包html的插件,并且能够自动把打包后的js添加上去。 第一个webpack插件 html-webpack-plugin 打包html作用 单入口

let HtmlWebpackPlugin = rquire('html-webpack-plugin') //类
plugins: [
    new HtmlWebpackPlugin({
        fileName: 'index.html',//默认就是index.html
        template: './public/index.html',
        //以public下的index.html为模板打包
        minify: {
            removeAttributeQuotes: true, //去除引号
            removeEmptyAttributes: true //清除所有的空属性
            ...
        },
        hash: true, //html引js避免缓存
        ...
    })
]

多入口自动打包引入js,利用循环

 plugins: [
        new HtmlWebpackPlugin({
            template: './public/index.html',
            filename: 'index.html',
            minify: {
                removeAttributeQuotes: true,
                removeEmptyAttributes: true
            },
            chunks: ['main'],//指定入口文件名字,自动引入相应js文件
            hash: true
        }),
        new HtmlWebpackPlugin({
            template: './public/index.html',
            filename: 'test.html',
            chunks: ['test'],
            hash: true
        })
    ]

html-webpack-plugin 参数链接

每次更改完代码,不能每次都手动打包,因此需要建立本地开发服务器,插件webpack-dev-server,并且它是在内存中打包,并不会看到dist目录,和build不同;并且修改代码会实时刷新。

devServer: {//开发服务器配置,这里单独列出,不在plugins内
        before(app){//如果不用转发,dev-server开启之前,前端自己mock数据
            app.get('/api/user',function(req,res){
                res.json({name:"fight"})
            })
        },
        contentBase: path.join(__dirname, 'dist'),//来自dist目录的文件提供服务,默认是output输出位置
        port: '3000',//更改端口号
        progress: true,//打包时候进度条
        compress:true //启动gzip压缩
        <!--proxy: {//node代理,假如页面访问api,node自动转发到3000端口-->
        <!--    '/api':{-->
        <!--        target: 'http://localhost:3000',-->
        <!--        pathRewrite: {-->
        <!--            '/api': ''//假如路径中有/api,重写置空-->
        <!--        } -->
        <!--    }-->
        <!--}-->
    },

2.1.1 编译css

loader有三种写法,字符串,数组和对象 当js需要样式的时候,import并不能解析,因此需要loader来转换模块

  1. yarn add css-loader style-loader
  module: {
        rules: [
            //css-loader 解析文中@import,style-loader 再以style方式插入样式
            //less 下载less less-loader,scss 下载node-sass sass-loader
            {
                test: /\.(css|less)$/,
                use: ['style-loader','css-loader','less-loader']//顺序不能变,否则报错
            }
        ]
    },

2. 除了以style方式插入样式,还可以以Link方式插入样式,这里需要用到插件mini-css-extract-plugin 假如css一些写法比如transform需要兼容其他浏览器,需要postcss-loader 和插件autoprefixer,并且根目录下新建postcss.config.js,内容如下

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

let MiniCssExtractPlugin = require(mini-css-extract-plugin)
{   
    test: /\.(css|less)$/,
    use: [
        MiniCssExtractPlugin.loader, //抽取样式到Link标签中 mini-css-extract-plugin 
        {
            loader: 'css-loader',
            options: {}
        },
        'less-loader',
        'postcss-loader' //兼容前缀
    ]
}
plugins: [
    new MiniCssExtractPlugin({
        filename: 'main.css' //href名字,如下图所示
    })
    ] 

2.1.2 编译js

目标:把ES6语法或者更高级别语法编译成ES5语法

  1. @babel/preset-env 预设是插件的集合,可以在plugins里配置,或者新建.babelrc文件;
  2. 匹配所有js文件,用babel转化 @babel/core babel核心包, babel-loader,babel加载器 @babel/preset-env预设转化语法
  3. 高级语法需要其他插件补充 @babel/plugin-proposal-decorators@babel/plugin-proposal-class-properties, @babel/plugin-transform-runtime, @babel-polyfill, @babel-runtime
{   
    test: /\.js$/,
    exclude: 'node_modules',
    use: 
    [
        {
            loader: 'babel-loader',
            options: {
                presets: ['@babel/preset-env'],
                "plugins": [
                    ['@babel/plugin-proposal-decorators',{"legacy":true}],//解析装饰器
                    ['@babel/plugin-proposal-class-properties',{"loose":true}],
                    [ "@babel/plugin-transform-runtime"] //该插件依赖安装@babel/runtime,解析高级语法,比如yiled
                ]
            }
        },
        'eslint-loader' //根目录新建.eslintrc.json 配置代码规则 add eslint eslint-loader -D
    ],
    exclude: /node_modules/
}

yarn add babel-polyfill 解析实例上的高级语法,在index中import进去 如果引入jquery,全局引用的话,暴露$,module中配置expose-loader

{
    test: require.resolve('jquery'),
    use: {
        loader: 'expose-loader?$',
    }
}

2.1.3 编译图片

引入图片有三种方式: 1:new Image() 2.css background:url() 3.html中创建 yarn add file-loader html-withimg-loader -D

 module: {
        rules: [
            {
                test: /\.html$/,
                use: 'html-withimg-loader' //解决html中引入img不识别图片的问题
            },
            {
              test:/\.(png|jpg|gif)$/,
              use: 'file-loader' //会自动创建一个MD5戳的名字,然后把图片拷贝到打包后的dist  
            }
        ]
    }

但是图片过多会引起资源请求过多,因此url-loader会优化这个问题 yarn add url-loader -D

{
  test:/\.(png|jpg|gif)$/,
  //url-loader会调用file-loader,优化:避免过多发请求,把小的图片或icon转换成base64,体积变大,不适用大图片
  //use: 'file-loader' //会自动创建一个MD5戳的名字,然后把图片拷贝到dist 
  use: {
      loader: 'url-loader',
      options: {
        limit: 8*1024 //小于8k的图片会转换成base64,避免请求  
      }
  } 
}

2.1.4 实时监控

 output:{},
 watch:true,//实时监控
 watchOptions:{
    poll:1000,
    aggregateTimeout: 2000,//如果2秒内没动作,自动打包, 防抖
    ignored:/node_modules/
},
module:{}

2.1.5 resolve

resolve: {
    modules: [path.resolve('node_modules')],//只查找当前路径的node_modules
    extensions: ['.js','.less','.json','.css'],//引入文件时省略扩展名
    alias: {//别名
        componets: './src/components/'
    }
}

2.1.6 插件

1.每次打包后都会生成新的dist目录,clean-webpack-plugin 自动清空打包后的目录

let CleanWebpackPlugin = require('clean-webpack-plugin')
 plugins: [
    new CleanWebpackPlugin('./dist'),
]
  1. 全局定义环境变量,webpack自带插件,js中PRODUCTION就是dev
let webpack = require('webpack')
plugins:[
    new Webpack.DefinePlugin({ //定义环境变量
        PRODUCTION:JSON.stringify('dev') // 定义PRODUCTION 是 dev环境
    })
    new Webpack.BannerPlugin('make by jeffywin'),//版权声明,自带插件
]

2.1.7 webpack优化

当mode为production的时候,默认css是不压缩的,需要插件,直接在module.export中配置optimization

let UglifyJsPlugin = require('uglifyjs-webpack-plugin')
let OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
optimization: {//优化
    minimizer: [//mode必须是production才有作用
        new UglifyJsPlugin({
            cache: true,
            parallel: true //并行,加速压缩
        }),
        new OptimizeCssAssetsPlugin({})//压缩css
    ]
}

2.1.8 webpack优化 DllPlugin DLLReferencePlugin 动态链接库

在通常的打包过程中,你所引用的比如bootstrap、react、react-router、antd、vue、vue-router、vuex 等等众多库每次开发打包都会打包进dist目录中,随着文件越来越大,打包的时间和代码体积也越来越大,由于这些库的内容基本不会发生改变,每次打包加入它们无疑是一种巨大的性能浪费。因此DllPlugin的出现就是解决这一问题,用某种方法实现了拆分 bundles,同时还大大提升了构建的速度。

  1. 额外创建一个js文件,DllPlugin插件会生成一个名为mainfest.json文件,这个文件是用来让DllReferencePlugin 映射到相关依赖上去
let path = require('path')
let webpack = require('webpack')
module.exports = {
    mode: 'development',
    entry: {
        react: ['react','react-dom',...] 
    },
    output: {
        filename: '_dll_[name].js', // 打包后的名字
        path: path.resolve(__dirname,'dist'),
        libraryTarget: 'commonjs2',
        libraryTarget: 'var',
        library: '_dll_[name]'//打包后会自动添加var _dll_[name] = {},来拿到内部的react,暴露出 (也叫做放入全局域) dll 函数
    },
    plugins:[
        new webpack.DllPlugin({//声明动态链接库
            name: '_dll_[name]', //暴露出的 DLL 的函数名
            path: path.resolve(__dirname,'dist','mainfest.json')//manifest json 文件的绝对路径 (输出文件)
        })
    ]
}

运行 yarn run build -- --config webpack.dll.js,会生成两个文件

  1. DllReferencePlugin是在webpack主配置文件中设置
  new ReferencePlugin({//开发优化 减少打包体积
        manifest:path.resolve(__dirname,'dist','mainfest.json') //html中还需要全局引用
    })

manifest包含 content 和 name 的对象,或者在编译时(compilation)的一个用于加载的 JSON manifest 绝对路径,也就是引用DllPlugin打包后的文件

按需打包前

按需打包后
只是提前打包react和reactDom的情况下,打包文件大小和时间大大缩短,如果按需加载更多,效率会更高。

总结: webpack.dll.js:DllPlugin 打包生成打包后的文件 和 mainfest.json关系映射文件
webpack.config.js: 主配置文件DllReferencePlugin通过mainfest映射到json文件,json文件调用提前打包好的缓存文件

2.1.9 webpack优化 happypack

当项目比较大时,通过happypack插件,它将任务分配给多个子进程去并发执行,子进程处理完再发给主进程,从而减少构建时间,但是项目比较小时,不建议使用,子进程分配时间反而可能增加构建时间。

使用:通过把原来module中的配置移到plugins中

let Happypack = require('happypack')
module: {
    rules: [
        {
            test: /\.js$/,
            use: 'happypack/loader?id=js',
            exclude: /node_modules/,
            include: path.resolve('src')
        }
    ]
}
plugins: [
new Happypack(
    {
        id: "js",
        use:[{
            loader: 'babel-loader',
            options: {
                presets: ['@babel/preset-env', '@babel/preset-react'],
                "plugins": [
                    ['@babel/plugin-proposal-decorators', { "legacy": true }],//解析装饰器
                    ['@babel/plugin-proposal-class-properties', { "loose": true }],
                    ['@babel/plugin-transform-runtime'] //该插件依赖安装@babel/runtime,解析高级语法,比如yiled

                ] //es7高级语法,顺序不能变  
            }
        }],
        //'eslint-loader' //根目录新建.eslintrc.json 配置代码规则
        }
    )
]

继续优化 yarn add webpack webpack-cli html-webpack-plugin @babel/core babel-loader @babel/preset-env @babel/preset-react

假如项目中引用了jquery等,可以用noParse来不去解析有关的依赖项 还有excludes,includes

module: {
        noParse: /jquery/,//不去解析jquery中的依赖项
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,//排除
                include: path.resolve(src),//包含
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: [
                            '@babel/preset-env',
                            '@babel/preset-react'
                        ]
                    }
                }
            }
        ]
    },

假如引用了moment时间插件,会自动下载本地语言文件,导致打包文件过大,可以用webpack自带插件 webpack.IgnorePlugin

plugins: [
        //如果引入了monent,去除掉本地的语言插件,如果需要可以单独引用
        new webpack.IgnorePlugin(/\.\/local/,/moment/),
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: './public/index.html'
        })
    ]

配置之后

多页面模块打包抽取公共代码和第三方库 假如同时引入了两个文件,或者jquery等第三方库,并且是多页面打包,可以将公共部分提前抽取出来打包

optimization: {
        splitChunks: {
            cacheGroups: {
                common:{
                    chunks: 'initial',
                    minSize: 0, //超过0个字节
                    minChunks: 2,//用过2次以上就抽离出来
                 },
                 venor: {//第三方
                    priority: 1,//权重,优先抽离
                    test: /node_modules/,
                    chunks: 'initial',
                    minSize: 0, //超过0个字节
                    minChunks: 2,//用过2次以上就抽离出来
                }
            }
        },
    },

Webpack本质是一种事件流机制,工作流程就是将各个插件串连起来,实现这一的核心就是Tapable