浅谈webpack,实现从零到一

2,917 阅读4分钟

webpack官网:webpack.docschina.org/

首先什么是webpack ?

  1. webpack是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个bundle。
  2. webpack 本身是基于node.js开发的
  3. 从 webpack v4.0.0开始,可以不用引入一个配置文件(零配置文件),然而webpack仍然还是高度可配置的。

下面我们分几个步骤,来详细看一下webpack

1. 安装webpack

我们以webpack第四代版本为主

为了防止全局安装webpack的版本冲突,我们在项目开发的时候基本上以安装在本地项目中为主;

//因为webpack是基于node.js开发的,所以安装webapck之前,我们必须安装node,然后npm会自动安装,然后基于npm命令安装webpack
$ npm init -y
$ npm install webpack webpack-cli --save-dev
OR
$ yarn add webpack webpack-cli -D

2. webpack的基础使用

初步体验(零配置)

/*
 * 默认会打包SRC目录中的JS文件(入口默认index.js)
 * 打包完成的目录默认是DIST/MAIN.JS
 *
 * npx:http://www.ruanyifeng.com/blog/2019/02/npx.html
 * 默认执行node_modules/bin/webpack.cmd文件
 * webpack默认支持CommonJS和ES6 Module的模块规范,依此进行依赖打包
 */
$ npx webpack

npx:从npm5.2版本后,提供了一个命令:npx,基于这个命令我们可以执行安装在本地的模块(基于npm安装,可以装在全局或者本地,装在全局的可以基于命令执行,而本地不能基于命令执行,当然可以配置)

$npx webpack基于npx执行了webpack,而这个命令就是实现打包部署的

自定义基础配置

虽然webpack第四代支持零配置,当然我们也可以根据需求来自己定义基础配置

首先我们创建一个名为webpack.config.js 或者 webpackfile.js的文件

//因为需要用node中的path内置模块,所以要导入
let path = require('path');
module.exports = {
    //=>打包模式  开发环境development  生产环境production
    mode: 'production',
    //=>入口
    entry: './src/index.js',
    //=>输出
    output: {
        //=>输出文件的文件名
        filename: 'bundle.min.[hash].js',
        //=>输出目录的"绝对路径"
        path: path.resolve(__dirname, 'dist')
    }
}

自定义配置文件名

  • $ npx webpack --config webpack.config.development.js
  • 可在package.json中配置可执行的脚本命令(区分开发环境)
//package.json
"scripts": {
    "serve": "webpack --config webpack.config.development.js",
    "build": "webpack --config webpack.config.production.js"
},

3. webpack-dev-server

webpack.js.org/configurati…

  • 安装:$ yarn add webpack-dev-server -D
  • 基础配置
/* webpack.config.js */
//=>配置DEV-SERVER
devServer: {
    //=>端口
    port: 3000,
    //=>显示编译进度
    progress: true,
    //=>指定访问资源目录
    contentBase: './dist',
    //=>自动打开浏览器
    open: true
}
/* package.json */
"scripts": {
    "serve": "webpack-dev-server",
    "build": "webpack"
}
  • $ npm run serve
  • $ npx webpack-dev-server

代码更改后,会自动重新编译,然后自动刷新页面

4. html-webpack-plugin

www.webpackjs.com/plugins/htm…

  • 安装:$ yarn add html-webpack-plugin -D
  • 在webpack.config.js中使用
//先导入这个插件
let HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    ...,
    //=>在webpack中使用插件
    plugins: [
        //因为导入的插件是一个类,所以我们用的时候需要new 一下
        new HtmlWebpackPlugin({
            //=>指定自己的模板
            template: './src/index.html',
            //=>输出的文件名
            filename: 'index.html',
            //=>给引入的文件设置HASH戳(清除缓存的),也可以在output中设置 filename: 'bundle.[hash].js' 来生成不同的文件
            hash: true,
            //=>控制是否以及以何种方式最小化输出 
            //=>https://github.com/kangax/html-minifier
            minify: {
                collapseWhitespace: true,
                removeComments: true,
                removeAttributeQuotes: true,
                removeEmptyAttributes: true
            }
        })
    ]
}

5. webpack中的加载器loader:处理样式的

  • 安装:$ yarn add css-loader style-loader less less-loader autoprefixer postcss-loader ... -D
  • 使用
module.exports = {
    //=>配置模块加载器LOADER
    module: {
        //=>模块规则:使用加载器(默认从右向左执行,从下向上)
        rules: [{
            test: /\.(css|less)$/, //=>基于正则表达式匹配哪些模块需要处理
            use: [
                "style-loader", //=>把CSS插入到HEAD中
                "css-loader", //=>编译解析@import/URL()这种语法
                "postcss-loader", //=>设置前缀
                {
                    loader: "less-loader",
                    options: {
                        //=>加载器额外的配置
                    }
                }
            ]
        }]
    }
}

postcss.config.js

//这个文件需要配合postcss-loader使用
module.exports = {
    plugins: [
        require('autoprefixer')
    ]
};
//package.json
"browserslist": [
    "> 1%",
    "last 2 versions"
]

6. mini-css-extract-plugin 抽离CSS内容

上面只是处理css,放到结构中,这个是单独把css隔离出来并压缩

www.npmjs.com/package/min…

  • 安装 $ yarn add mini-css-extract-plugin optimize-css-assets-webpack-plugin uglifyjs-webpack-plugin -D
let MiniCssExtractPlugin = require('mini-css-extract-plugin'),
    OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin'),
    UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
    //=>设置优化项
    optimization: {
        //=>设置压缩方式
        minimizer: [
            //=>压缩CSS(但是必须指定JS的压缩方式)
            new OptimizeCssAssetsWebpackPlugin(),
            //=>压缩JS
            new UglifyjsWebpackPlugin({
                cache: true, //=>是否使用缓存
                parallel: true, //=>是否是并发编译
                sourceMap: true, //=>启动源码映射(方便调试)
            })
        ]
    },
    plugins: [
        //=>使用插件
        new MiniCssExtractPlugin({
            //=>设置编译后的文件名字
            filename: 'main.css'
        })
    ],
    module: {
        rules: [{
            test: /\.(css|less)$/,
            use: [
                // "style-loader",
                //=>使用插件中的LOADER代替STYLE方式
                MiniCssExtractPlugin.loader,
                "css-loader",
                "postcss-loader",
                "less-loader"
            ]
        }]
    }
}

7. 基于babel实现ES6的转换和ESLint语法检测

在第六步中,需要对于ES6中的一些语法进行处理!

babeljs.io/

eslint.org/

  • 安装 $ yarn add babel-loader @babel/core @babel/preset-env @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators @babel/plugin-transform-runtime -D
  • 安装 $ yarn add @babel/runtime @babel/polyfill
  • 安装 $ yarn add eslint eslint-loader -D
module.exports = {
    ...,
    module: {
        rules: [...,{
            test: /\.js$/,
            use: [{
                loader: 'babel-loader',
                options: {
                    //=>转换的语法预设(ES6->ES5)
                    presets: [
                        "@babel/preset-env"
                    ],
                    //=>基于插件处理ES6/ES7中CLASS的特殊语法
                    plugins: [
                        ["@babel/plugin-proposal-decorators", {
                            "legacy": true
                        }],
                        ["@babel/plugin-proposal-class-properties", {
                            "loose": true
                        }],
                        "@babel/plugin-transform-runtime"
                    ]
                }
            }], //=>, "eslint-loader"
            //=>设置编译时忽略的文件和指定编译目录
            include: path.resolve(__dirname, 'src'),
            exclude: /node_modules/
        }]
    }
}

8. 暴露全局loader

  • $ yarn add expose-loader -D
  • 前置加载器、后置加载器、普通加载器...

比如想直接使用jquery或者$

//=>内联加载器
import jquery from 'expose-loader?$!jquery';
console.log(window.$);

{
    //=>只要引入JQUERY就在全局注入$
    test: require.resolve('jquery'),
    use: ['expose-loader?$']
}
let webpack = require('webpack');
module.exports = {
    plugins: [
        //=>在每个模块中都注入$
        new webpack.ProvidePlugin({
            '$': 'jquery'
        })
    ],
}

//=>页面中
console.log($);//这样就不会报错了

9. webpack中图片的处理和分目录分发

使用图片有三种方式

  • 在js中创建img
  • 在css中设置背景图片
  • 在html中写死

安装 $ yarn add file-loader url-loader html-withimg-loader -D

module.exports = {
    ...,
    module: {
        //=>模块规则:使用加载器(默认从右向左执行)
        rules: [..., {
            //还有很多图片格式。。。
            test: /\.(png|jpg|gif)$/i,
            use: [{
                //=>把指定大小内的图片BASE64
                loader: 'url-loader',
                options: {
                    limit: 200 * 1024,
                    outputPath:'/images'
                }
            }],
            include: path.resolve(__dirname, 'src'),
            exclude: /node_modules/
        }, {
            test: /\.html$/,
            use: ['html-withimg-loader']
        }]
    }
}
  • 最后实现文件分目录发布
module.exports = {
    output: {
        //=>配置引用前缀(所有资源前加这个地址)
        publicPath: './'
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: 'css/main.css'
        })
    ],
    module: {
        //=>模块规则:使用加载器(默认从右向左执行)
        rules: [...,{
            test: /\.(png|jpg|gif)$/i,
            use: [{
                options: {
                    outputPath: 'images'
                }
            }]
        }]
    }
}

一个比较完整的webpack配置

/* 
    在这个文件中设置我们自定义的打包规则
        1. 所有的规则都写在module.exports={}中
*/
let path = require('path');
let HtmlWebpackPlugin = require('html-webpack-plugin');
let MiniCssExtractPlugin = require('mini-css-extract-plugin'), //把css单独分离出来
    OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin'), //压缩css
    UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin'); //压缩js
let webpack = require('webpack');
//导入进来的插件都是一个类  new HtmlWebpackPlugin({});

module.exports = {
    //配置优化规则
    optimization: {
        //设置压缩方式
        minimizer: [
            //压缩css  (产生问题:JS压缩不在执行自己默认的压缩方式了,也走的是这个插件,从而导致无法压缩,所以必须设置JS的压缩方式)
            new OptimizeCssAssetsWebpackPlugin(),
            //压缩js
            new UglifyjsWebpackPlugin({
                cache: true, //是否使用缓存
                parallel: true, //是否是兵法编译
                sourceMap: true, //启动源码映射(方便调试)
            })
        ]
    },
    //配置环境 开发环境development  生产环境production(默认)
    mode: 'production',
    //入口
    entry: ['@babel/polyfill', './src/index-my.js'],
    //出口
    output: {
        //输出文件的文件名
        //bundle.min.[hash].js 让每一次生成的文件名都带着hash值
        filename: 'bundle.min.js',
        // filename: 'bundle.min.[hash].js',
        //输出的目录必须是绝对路径,__dirname当前目录
        path: path.resolve(__dirname, 'dist'),
        publicPath: './' //给编译后引入资源地址前面设置的前缀
    },
    //关于webpack-dev-server的一些配置  执行命令:webpack-dev-server --config xxx.js
    //(特点:服务启动后,默认是不关闭的,当我们修改src中的源文件,他会自动进行编译,然后自动刷新浏览器,
    //类似于vscode中的Live Server插件,实时刷新)
    devServer: {
        //创建服务指定的端口号
        port: 3000,
        //显示打包编译进度
        progress: true,
        //指定当前服务处理资源的目录
        contentBase: './dist',
        //编译完成后,自动打开浏览器
        open: true
    },
    //使用插件 (数组)
    plugins: [
        new HtmlWebpackPlugin({
            //不指定模版会按照默认模版创建一个html页面,当然真实项目中一般都是把自己写好的html进行编译
            template: './src/index.html',
            //输出的文件名
            filename: 'index.html',
            //让我们引入js后面加上hash戳(清除缓存),但是真实项目中我们一般都是每一次编译生成不同的js文件引入
            hash: true,
            //控制压缩
            minify: {
                collapseWhitespace: true,
                removeComments: true,
                removeAttributeQuotes: true,
                removeEmptyAttributes: true
            }
        }),
        new MiniCssExtractPlugin({
            //指定输出的文件名
            filename: 'main.min.css'
        }),
        //在每个模块中都注入$
        new webpack.ProvidePlugin({
            '$': 'jquery'
        }),
    ],
    //使用加载器loader处理规则
    module: {
        rules: [{
            //基于正则匹配处理哪些文件
            test: /\.(css|less)$/,
            //使用哪一个加载器,控制使用的loader(有顺序的:从右到左执行)
            use: [
                // "style-loader", //把编译好的css插入到页面的head中(内嵌式)
                MiniCssExtractPlugin.loader, //使用插件中的loader代替style方式
                "css-loader", //编译解析@import/URL()这种语法
                // "postcss-loader",//设置前缀的加载器
                {
                    loader: "postcss-loader",
                    options: {
                        ident: 'postcss',
                        plugins: [
                            require('autoprefixer')
                        ]
                    }
                },
                {
                    loader: "less-loader", //编译less
                    options: {
                        //加载额外的配置
                    }
                }
            ]
        }, {
            test: /\.js$/,
            //处理编译JS的loader
            use: [{
                loader: 'babel-loader',
                options: {
                    //转换的语法预设(ES6->ES5)
                    presets: [
                        "@babel/preset-env"
                    ],
                    //=>基于插件处理ES6/ES7中CLASS的特殊语法
                    plugins: [
                        ["@babel/plugin-proposal-decorators", {
                            "legacy": true //处理装饰器
                        }],
                        ["@babel/plugin-proposal-class-properties", {
                            "loose": true //处理属性
                        }],
                        "@babel/plugin-transform-runtime"
                    ]
                }
            }],
            //设置编译时忽略的文件和指定编译目录
            include: path.resolve(__dirname, 'src'), //编译的
            exclude: /node_modules/ //忽略的·
        }, {
            //图片处理
            test: /\.(png|jpg|gif|jpeg|ico|webp|bpm)$/i,
            use: [{
                loader: 'url-loader',
                options: {
                    //只要图片小于200KB,在处理的时候直接base64
                    limit: 2 * 1024,
                    //控制打包后图片所在的目录
                    outputPath: 'images'
                }
            }]
        }, {
            //处理HTML文件中导入的img文件
            test: /\.(html|htm|xml)$/i,
            use: ['html-withimg-loader']
        }]
    }
}