资讯专栏INFORMATION COLUMN

一看就懂之webpack基础配置

woshicixide / 1775人阅读

摘要:一看就懂之基础配置一简介本质上,是一个现代应用程序的静态模块打包器。属性表示的是的上下文目录,配置入口文件的时候,如果入口文件使用的是相对路径,那么就是相对于所在的目录。通常用于指定以何种方式导出库,通常用于指定接收库的名称。

一看就懂之webpack基础配置 一、webpack 简介
本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
简单说,webpack可以看做是一个模块打包机,主要作用就是: 分析你的项目结构,找到JavaScript模块以及一些浏览器不能直接运行的拓展语言(sass、less、typescript等),然后将它们打包为合适的格式以供浏览器使用。

webpack主要实现的功能:

代码转换(如: ES6转换ES5、sass和less转换为css等)
文件优化(如: 将模块内容进行压缩)
代码分割(如: 多页面应用公共模块的抽离、路由懒加载)
模块合并(如: 按照不同的功能将多个模块合并为一个模块)
自动刷新(如: 启动本地服务,代码更新后进行自动刷新)
代码校验(如: 添加eslint进行代码规范检查)
自动发布(如: 应用打包完成后,自动发布)
二、webpack 安装

在webpack 3中,webpack本身和它的CLI以前都是在同一个包中的,但在第4版之后,已经将两者分开来更好地管理它们,所以安装webpack4之后的版本,要同时安装webpack和webpack-cli

注意,安装webpack的时候,必须进行本地安装才能生效,否则会报错,如:
但是全局安装了也有一个好处,那就是可以在项目根目录下直接执行webpack即可完成项目的打包,如果没有进行全局安装,那么可以通过npx直接执行项目本地安装的模块,即 npx webpack也可以完成项目的打包。
三、webpack 基础配置

webpack是支持零配置的,即不需要配置文件即可完成打包,其默认入口文件为项目根目录下的src目录下的index.js文件,其默认出口为项目根目录下的dist目录的main.js

如果没有给webpack添加配置文件,那么webpack的打包能力就会非常弱,webpack执行的时候默认会加载项目根目录下的webpack.config.js文件,注意,该配置文件是一个js文件,而不是json文件,并且其是通过node去执行的,所以其完全支持node语法,即node中能用的,在配置文件中都可以用
webpack配置文件必须要对外暴露一个对象,即通过module.exports进行对外暴露,其中的所有配置都必须写在这个对外暴露的对象中。

context
context属性表示的是webpack的上下文目录,配置入口文件的时候,如果入口文件使用的是相对路径,那么就是相对于context所在的目录。

context默认值为执行webpack命令时所在的当前工作目录,通常是在项目根目录下执行webpack命令,所以可以认为其值默认为项目根目录,所以如果入口文件路径写成相对路径,最好将context配置成context: path.resolve(__dirname),以防止在非项目根目录下执行webpack命令时找不到入口文件路径而报错。

entry

entry用于配置模块的入口文件,可以配置多个,webpack将从入口文件开始搜索以及递归解析所有入口文件依赖的模块,其是必填的,如果配置文件中没有entry则会报错。entry的属性值可以是表示路径的单个字符串也可以是数组,数组中的元素为入口文件的路径,还可以是对象,对象的属性名为入口文件的chunk名,即打包后输出文件的名字,属性值为入口文件的路径。注意,入口文件的路径可以是绝对路径也可以是相对路径,相对路径默认是以配置文件中的context属性值表示的路径
module.exports = {
    entry: "./foo.js" // 属性值为一个表示路径的字符串
}
其输出结果文件取决于配置文件的output配置,如果output.filename没有配置,则默认输出为main.js,如果output.filename值为指定的名称,则输出结果为output.filename的属性值
module.exports = {
    entry: [ "./foo.js", "./bar.js"] // 属性值为一个数组
}
其输出结果文件也是取决于配置文件的output配置,只不过,其会将foo.js和bar.js一起打包输出为一个文件,如果output.filename没有配置,则默认输出为main.js,如果output.filename值为指定的名称,则输出结果为output.filename的属性值
module.exports = {
    entry: { // 属性值为一个对象
        a: "./src/bar.js",
        b: "./src/foo.js",
        c: "./src/index.js"
    }
}
其输出结果不再取决于output.filename的配置,因为entry已经指定好了模块输出的chunk名,即会分别输出a.js、b.js和c.js三个文件,并且此时output.filename属性值不能配置为一个固定名称的输出文件,因为入口文件有多个,必然输出文件也会有多个
chunk和module的区别,二者都是表示模块,但是module可以看做是具有独立功能的小模块,即小块,也就是打包之前,程序员编写的一个一个的文件,每个文件称为一个module;而chunk则可以看做是由多个小module打包成的大模块,即大块

output
output配置的是如何输出最终想要的代码,output是一个object。

path: 用于配置打包后输出文件的本地存放目录,必须是绝对路径,当然也可以不配置,因为如果没有配置path,那么会自动在执行webpack命令时所在的目录下自动创建dist目录并将打包结果输出到dist目录下,与context的配置路径无关

module.exports = {
    output: {
        path: path.resolve(__dirname, "./dist") // 必须是绝对路径
    }
}

filename: 用于配置输出文件的名称,如果只有一个输出文件,那么可以配置成静态不变的文件名,如:

module.exports = {
    output: {
        filename: "bundle.js"
    }
}

但是,如果有多个chunk要输出时,即入口文件配置了多个时,那么filename就不能配置成静态不变的了,就必须借助模板和变量了,常见的两个变量,如:
[name]: 可以自动获取到入口文件配置的chunk名称;
[hash]: 可以自动生成hash值,hash值的长度是可以指定的,默认为20位;

module.exports = {
    output: {
        filename: "[name][hash:8].js" // 以入口文件设置的chunk作为输出名,并且指定hash值为8位
    }
}

library和libraryTarget: 用于指定将模块的输出结果挂载到哪个地方或者以什么样的方式导出库(模块输出结果)。二者通常要搭配一起使用。
libraryTarget通常用于指定以何种方式导出库,library通常用于指定接收库的名称。
我们将入口的一个或多个js文件打包输出后的结果也是一个js文件,在没有配置library和libraryTarget的时候,这个输出的js文件中包含了一个匿名自执行函数, 即输出文件的执行结果没有被任何东西接收,我们引入输出文件执行后不会得到任何结果。 如:

(function(){
    console.log("foo");
    return "foo"; // 虽然有返回值,但是匿名自执行函数执行完毕后拿不到任何结果
})();
// 我们以var 变量 的方式来接收函数的返回值
var foo = (function(){ // 匿名自执行函数执行完毕后就会将函数返回值保存到foo变量上
    console.log("foo");
    return "foo";
})();
console.log(`foo is ${foo}`);
打包后的输出文件的输出结果(导出结果),就是入口文件的输出结果(导出结果),即入口文件通过export、exports、module.exports等输出(导出)的结果

var: 将libraryTarget设置为var, 同时指定一个自定义的变量名来接收模块的输出,这个自定义的变量名就是library的属性值

module.exports = {
    output: {
        filename: "bundle.js",
        path: path.resolve(__dirname, "./dist/"),
        libraryTarget: "var",
        library: "test"
    }
}

模块的输出结果将会赋值给test变量,其输出文件bundle.js内容大致如下:

var test = (function(modules){
    return result; // 返回值result将会被赋值给test变量 
})();

commonjs: 将libraryTarget设置为commonjs, 即通过commonjs规范导出,同时指定一个自定义的变量名来接收模块的输出,这个自定义的变量名就是library的属性值, 只不过这个自定义的变量名是exports的属性名,如:

module.exports = {
    output: {
        filename: "bundle.js",
        path: path.resolve(__dirname, "./dist/"),
        libraryTarget: "commonjs",
        library: "test"
    }
}

模块的输出结果将会赋值给exports["test"]上,其输出文件bundle.js内容大致如下:

exports["test"] = (function(modules){
    return result; // 返回值result将会被赋值给exports["test"]
})();

commonjs2: 将libraryTarget设置为commonjs2,即通过commonjs2规范导出,此时library的配置将无意义,因为commonjs2的输出是固定的module.exports,所以不需要指定library了,如:

module.exports = {
    output: {
        filename: "bundle.js",
        path: path.resolve(__dirname, "./dist/"),
        libraryTarget: "commonjs2"
    }
}

模块的输出结果将会被赋值到module.exports上,其输出文件bundle.js内容大致如下:

module.exports = (function(modules){
    return result; // 返回值result将会被赋值给module.exports
})();
commonjs和commonjs2的区别在于,commonjs只能使用exports进行导出,而commonjs2在commonjs的基础上增加了module.exports进行导出;

this: 将libraryTarget设置为this, 那么此时library配置的变量名将作为this的属性名来接收模块的导出结果,如:

module.exports = {
    output: {
        filename: "bundle.js",
        path: path.resolve(__dirname, "./dist/"),
        libraryTarget: "this",
        library: "test"
    }
}

模块的输出结果将会被赋值到this["test"] 上,其输出文件bundle.js内容大致如下:

this["test"] = (function(modules){
    return result; // 返回值result将会被赋值给this["test"]
})();

同理libraryTarget的属性值还可以是windowglobal,这里就不一一列举了。

publicPath
publicPath用于配置打包资源发布到线上时服务器的url地址,打包完成后,html文件中如果引入了js、image、css等资源,那么都会在前面加上publicPath所表示的路径

module.exports = {
    output: {
        filename: "bundle.js",
        path: path.resolve(__dirname, "./dist/"),
        publicPath: "http://www.lihb.com/"
    }
}

// index.html




    
四、webpack 打包输出后的内容分析

webpack打包输出后的结果默认是一个匿名自执行函数,匿名自执行函数传递的参数为一个对象,对象的属性名为入口文件的路径名属性值为一个函数,函数体内部通过会执行eval(),eval()方法的参数为入口文件的内容字符串,而这个匿名自执行函数,内部有一个自定义的__webpack_require__方法,该方法需要传入入口文件的路径名作为参数,匿名自执行函数执行完成后会返回__webpack_require__的结果,而__webpack_require__()方法内部执行的时候,会首先创建一个module对象module对象里面有exports属性,属性值为一个空的对象,用于接收入口文件的模块输出,如:

(function(modules) {
    function __webpack_require__(moduleId) { // 传入入口文件的路径
        var module = installedModules[moduleId] = { // 创建一个module对象
             i: moduleId,
             l: false,
             exports: {} // exports对象用于保存入口文件的导出结果
         };
        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); // 执行入口文件
        return module.exports; // 返回模块输出结果
    }
    return __webpack_require__(__webpack_require__.s = "./src/bar.js"); // 返回入口文件
})({
     "./src/bar.js": (function(module, exports) {
         eval("module.exports = "bar";");
     })
  });
所以不管入口文件是以ES6模块的方式输出还是以commonjs模块的方式输出,最终入口文件的模块输出结果都会被绑定到__webpack_require__方法中定义的module对象的exports属性上,只不过,如果是以commonjs的方式输出,那么入口文件的输出结果将会直接替换掉__webpack_require__方法中定义的module对象的exports属性;如果是以ES6模块的方式输出,则是在__webpack_require__方法中定义的module对象的exports属性值中添加一个default属性或者具体变量名来保存入口文件的输出
五、webpack 本地服务器配置

为了更方便调试,我们需要用到webpack的本地http服务器功能,要想使用webpack提供的Web服务器功能,我们需要安装webpack-dev-server模块,webpack-dev-server会启动一个web服务器用于实现网页请求,也可以监听文件的变化自动刷新网页。

webpack-dev-server模块安装完成后,我们可以在项目根目录下运行npx webpack-dev-server,其和webpack命令一样,如果没有配置文件,则使用内置默认配置进行打包输出,如果有则使用配置文件中的配置,只不过其不会将打包结果输出到指定的目录中,因为webpack-dev-server会忽略配置文件中的output.path配置,其会将打包输出结果保存到内存中。webpack-dev-server启动后会默认将启动devServer时所在的目录作为根目录,即执行npx webpack-dev-server命令时所在的目录

webpack提供了一个devServer属性用于配置启动的服务器的一些参数,当然webpack本身是无法识别devServer属性配置的,只有通过webpack-dev-server去启动webpack时,devServer的配置才会生效

module.exports = {
    devServer: {
        port: 3000, // 让devServer监听3000端口
        contentBase: "./dist", // 将当前项目的dist目录作为devServer的根目录
        progress: true, // 显示打包进度条
        compress: true // 是否启用Gzip压缩,默认为false
    }
}
webpackDevServer启动后,默认会自动监听打包源文件的变化,如果修改了打包源文件,那么会自动重新打包到内存,并且会自动刷新浏览器,但是自动刷新浏览器功能必须将target设置成web,否则自动刷新功能将会失效,比如target为node就无法起作用。
六、webpack 插件配置

在不使用插件的时候,webpack默认只能打包输出js文件,如果我们想要输出其他格式的文件到输出目录中,那么我们必须使用插件。webpack提供了一个plugins属性用于配置使用到的插件,其属性值为一个数组,数组中的元素为插件对象,通常插件都是一个类,我们需要通过插件类来创建一个插件对象
html-webpack-plugin
该插件可以指定一个html文件,webpack会将该html文件打包输出到输出目录中,同时会将打包输出后的文件自动插入到该html文件中,即让该html文件自动引入打包后的js文件

module.exports = {
    plugins: [
        new HtmlWebpackPlugin({
            template: "./src/index.html", // 要打包输出哪个文件,可以使用相对路径
            filename: "index.html", // 打包输出后该html文件的名称
            minify: {
                removeAttributeQuotes: true, // 去除html文件中的引号
                collapseWhitespace: true // 合并空格,即将html进行单行显示
            },
            hash: true // 向html文件中引入打包后的js时候带上hash值
        })
    ]
}
html插件中配置了hash为true, 是在引入打包后的js的时候带上hash值,如:
七、webpack 模块配置

webpack默认将所有格式的文件都当做模块进行处理,但是wepback默认只能处理js模块。如果在js中通过require引入了其他格式的模块(文件),那么webpack就必须通过安装合适的模块加载器,才能正确解析对应的模块内容,webpack提供了一个module属性,用于进行模块解析器的配置,其属性值为一个对象,对象中有一个rules属性,其属性值为一个数组,数组中的元素为一个对象,该对象主要完成两件事,匹配对应格式的文件,并且使用对应模块加载器进行加载,匹配使用的是test属性,属性值为一个正则表达式,【使用】使用的是use属性,属性值可以是字符串也可以是数组,如果只有一个模块加载器的时候,可以使用字符串的形式,如果有多个模块加载器的时候,那么就需要使用数组的形式,当然,如果模块加载器需要传递参数配置,那么可以将模块加载器写成对象的形式,通过loader属性指定模块加载器名称,通过options属性传递参数配置。

① 处理css样式,需要使用到css-loader和style-loader。
首先需要安装css-loader和style-loader。
css-loader必须同时和style-loader一起使用才能正确加载css文件,一个负责加载,一个负责插入css-loader负责加载css, 即在js文件中能够通过require的方式引入css,即加载和解析css,同时支持在css文件中使用@ import的方式引入其他css文件style-loader负责将加载并解析好的css文件插入到html文件中去,从名字可以看出其是在html文件中生成style标签来引入css文件,loader的执行顺序是从右向左,所以必须先加载然后再插入

比如,打包入口文件index.js中通过require的方式引入了一个index.js文件,即require("./index.css"),那么webpack需要进行如下配置:

module.exports = {
    module: {
        rules: [
            {
                test: /.css$/, // 匹配以.css结尾的文件
                use: [ // 并交给css-loader和style-loader进行处理
                    {
                        loader: "style-loader", // 以对象的形式配置loader
                        options: { // 通过options给loader传递参数
                            insertAt: "top" // 默认为bottom, 将加载的css文件插入到head标签的最上面,即优先级最低,会被覆盖
                        }
                    },
                    "css-loader" // 直接以字符串的形式配置loader
                ]
            }
        ]
    }
}
打包输出后,会将index.css中的内容放到