技术干货我的大学webpack

从0到1教你学习并配置webpack

2019-12-31  本文已影响0人  Cryptic

抓住2019年的尾巴,迎接2020年的到来~

给自己的2019年画上一个完美的句号。

引言

webpack 对于现在的前端开发的你来说,不算陌生,你或许没有真正去了解过它是如何使用的,但你的项目中确实使用过它,因为你项目的打包编译都跟他息息相关~

前阵子刚好在研究webpack以及其源码相关的知识,如果你跟我一样,好奇webpack又是怎么工作的?那些奇奇怪怪的配置都是什么东西?分别代表什么意思?

那你不妨花几分钟阅读一下,跟我一起学习回顾一下如何从0到1去了解webpack的知识, 配置你项目需要的webpack~

pass:本文以 webpack4 为例~

什么是webpack?

什么是 webpack

官方文档 是这么说:

webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle

其实,它就是一个模块化打包工具,它所做的事情就是分析你的项目结构,找到JavaScript模块并对其进行代码转换(scss 转换为cssTypescript 转换为 Javscript),文件优化(压缩JavaScriptCSSHTML、图片等),模块合并 (把模块分类合并成一个文件)等一系列的操作,最终打包为合适的格式在让你的项目可以在浏览器中运行~

盗用官网的一张图,其主要工作原理就如下图所示~

核心概念

在真正上手 webpack 之前,我们需要对其几个 核心概念 有所了解

整个流程串起来大概就是:

webpack启动后会从Entry里配置的Module开始递归解析 Entry 依赖的所有 Module。 每找到一个 Module,就会根据配置的Loader去找出对应的转换规则,对 Module 进行转换后,再解析出当前 Module 依赖的 Module

这些模块会以 Entry 为单位进行分组,一个 Entry 和其所有依赖的 Module 被分到一个组也就是一个Chunk。最后 webpack 会把所有 Chunk 转换成文件输出,在这整个流程中 webpack 会在不同的生命周期内执行配置的 Plugin 里定义的逻辑。

了解了上面的一些概念和流程之后,接下来,我们一步步来配置,打包我们的项目~

初始化项目

创建文件夹

首先,我们创建一个你喜欢的文件夹,并通过 npm init -y 来初始化项目配置,并在其目录下创建一个我们源代码的目录src和一个打包后的文件输入目录dist

创建入口文件 index.js

然后我们在 src 目录下创建一个 index.js 文件,作为我们的打包入口文件,并写上我们熟悉的 console.log('hello world'); 作为打包内容。

安装webpack

webpack4 之后将 webpackcli 分成了两个包,我们需要通过 npm install webpack webpack-cli -D 安装我们所需的 webpack 依赖。

ok~ 准备就绪!

遗憾的是,你还是不能直接进行打包,因为我们的 webpack 是在项目下安装的,所以不能直接运行,想要正确运行webpakc我们可以有下面2种方式:

为了贴近我们的项目,这里我们选择第二种方式

配置package.json脚本

我们打开package.json文件,并在 scripts 中配置下面的代码:

    "dev" : "webpack --mode development",
    "build" : "webpack --mode production",

dev 表示开发模式,build 是生产模式,不同在于 dev 会有很多方便我们开发调试的功能,比如代码不压缩混淆,有开发服务器之类的,我们学习阶段采用 dev 即可~

创建配置文件

这时候,我们已经可以通过 npm run dev 命令打包我们的代码,并在dist目录下看到我们打包后的 main.js 文件了~

你可能很诧异的蹦出一句:

因为到此,你发现自己什么都还没配置,咋就可以打包了!!

这是因为 webapck4 为了简化我们开发人员繁琐的配置工作,在内部写好了一套配置,惊不惊喜,意不意外!!

那我这肯定不能答应了,不然不就打我脸了么!

要想加载自己的配置,我们需要在我们的项目根目录下创建 webpack.config.js 文件,并创建我们的基础配置。

//path
const path = require('path');

//配置信息
module.exports = {
    //入口文件
    entry : './src/index.js' ,
    //出口文件
    output : {
        //打包后路径,只能写绝对路径
        path : path.resolve(__dirname,'dist'),
        //打包后文件名
        filename : '[name].[hash].js'
    },
    //模块转换规则
    module : {
    
    },
    //插件
    plugins : [
    
    ],
    //开发服务器
    devServer : {
    
    }
}

我们注意到 output 中的 filename 中有一个 [name][hash],其中nameentry 的名字,默认是 mainhash 是打包根据内容后计算出的一个 hash值,保证文件的唯一性,可以通过[hash:8] 表示取其前8位。

现在运行 npm run devdist 目录下就会打包出类似 main.47bfc309d4ba9b75d346.js 的文件。

这里,我们为了方便,先将其改成 main.js

创建index.html文件

为了方便我们在浏览器测试,我们在我们的 dist 目录创建一个 index.html ,并引入我们编译好的main.js文件,如下:

//index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>webpack</title>
</head>
<body>
<div id="app">webpack</div>
<script src="main.js"></script>
</body>
</html>

我们在 index.js 文件下加上这么一句话:

document.querySelector('#app').style.color='red';

测试一下打包后的文件好不好使。

运行 npm run dev,打开我们的 index.html 预览,不出意外的话,结果应该和我一样,页面的 webpack 文字变红,控制台输出 hello world

至此,我们才将我们的项目基础配置搞定~

加载css

如果我们现在想加入 css 文件,优化我的样式,首先在 src 目录下创建 stylesheets 目录,并添加 index.css 文件,我们就先以 body { background : #f2f2f2; } 为例,之后在 index.js 中通过 import './stylesheets/index.css' 的方式导入样式文件。

如果现在我们直接进行打包肯定会报错,因为 css 文件并不是 js 模块,webpack 在打包的时候没法直接处理,需要借助转换工具,这转换工具就是 Loader

Loader 就是文件转换器,通过使用不同的Loaderwebpack就可以把不同的文件都转成js文件,比如CSSES6/7JSX等,它通常有下面几个配置项:

接下来,我们来使用 style-loadercss-loader 来处理我们的 css 文件。

执行 npm install style-loader css-loader -D 进行安装 loader

webpack.config.js 中的module下面添加解析规则:

module : {
    rules : [{
        test : /\.css$/,
        use : ['style-loader','css-loader']
    }]
},

通常来说,loader有下面三种书写方式,上面是通过 use 方式,还有两种方式分别是 直接使用 loader 和使用 use + loader

//直接使用loader

module: {
    rules: [{
        test: /\.css/,
        loader:['style-loader','css-loader']
    }]
}
//use + loader 的方式

module: {
    rules: [{
        test: /\.css/,
        include: path.resolve(__dirname,'src'),
        exclude: /node_modules/,
        use: [{
            loader: 'style-loader',
            options: {
                insert:'top'
            }
        },'css-loader']
    }]
}

要注意的是配置多个 loader有顺序 的,webpack 会安装配置的 loader 顺序 从右向左 执行的,配置的时候要格外注意!

拿我们上面的 style-loadercss-loader 来说,两个loader配置的顺序不可调换,因为 css-loader是解析处理css里的url路径,并将css文件转换成一个模块,style-loader是 将css文件变成style标签插入到head中的。

现在我们 npm run dev 试试,打包没有报错,预览 index.html 也成功生效,很赞!

配置开发服务器

到此你有没有发现,我们在平时的开发过程中,怎么没有遇到说让我每次通过预览dist下的 index.html来看我们打包效果的,这是由于我们平时的开发中会在自己本地起一个服务器,来帮我们做这件事。

现在,我们也来试试~

此时,直接运行 npm run dev 是看不到我们预期的效果的,因为我们还没有对我们的服务器进行配置。

我们在 webpack.config.jsdevServer 下添加如下配置:

devServer : {
    contentBase : path.resolve(__dirname,'dist'),
    host : 'localhost' ,
    port : 8000 ,
    compress : true
}

ok,运行 npm run dev ,是不是效果非常棒!

要注意的是,webpack dev server 产生的打包文件是在 内存中!,是 内存中!,硬盘是访问不到的,怎么验证这一点呢?

很简单,你可以删除掉你dist目录下的 main.js ,重新运行 npm run dev ,你可以看到 dist 目录下并没有 main.js,但你访问 localhost:8000 确实能正确访问,并且访问 http://localhost:8000/main.js 也能看到打包后的源码,证明它产生的打包文件确实是在你的 内存中

注意,我们这里打包出来的只有一个 main.js 文件和一个index.css文件,index.html 文件是我们手动添加进去的,不是打包产生的,如果删掉 index.html 是无法正确访问页面的,内存里可没有 index.html 文件。

我们可以在终端看到以下这些内容:

这些都是我们在启动开发服务器的时候 webpack 给我们的页面注入的 websocket 连接,你可以在你的页面调试器的 network 中的 ws 里看到,

其重要作用就是,监控你文件的变化,可以帮助你重新刷新页面,让你看到更改后的效果,你可以修改一下 index.js 文件试试~

自动生产HTML文件

我们之前是在 dist 目录下写好了 index.html 文件,并在里面通过 script 标签引入我们打包后的内容,即 main.js ~

还记得我们之前打包后的 filename 中可以加入一个 hash 值也区别不同的文件,如果 hash 值发生变化了,我们的 index.html 岂不是找不到资源了?所以我们希望自动能产出HTML文件,并在里面引入产出后的资源,这样就不必为上面的问题担心了。

我们删除掉 dist 目录下的 index.html 文件,并在 src 目录下创建一个 index.html 文件,我们称它为模板,以它为模板产生 html 文件。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <title>webpack</title>
</head>
<body>
    <div id="app">webpack</div>
</body>
</html>

这时,我们需要接触到第一个插件 html-webpack-plugin!

之前也说了,插件在 webpack 中有很重要的作用,在 webpack 的构建流程中,plugin 用于处理更多其他的一些构建任务,模块代码转换的工作由 loader 来处理,除此之外的其他任何工作都可以交由 plugin 来完成。

//自动产出HTML模版
new HtmlWebpackPlugin({
    template: './src/index.html',
    filename: 'index.html',
    hash: true,
    minify: {
        removeAttributeQuotes: true,
        removeComments: true 
    }
})

其中 template 是指定模板文件,filename 是指定产出后的文件名,hash 是为了避免缓存,可以在产出的资源后面添加hash值,minify 是 压缩相关的配置,minify.removeAttributeQuotes 是为了去掉属性的双引号,minify.removeComments 是为了压缩文件,删除模板中的注释。

此时,我们运行 npm run dev 访问 localhost:8000 发现已经可以正常访问了,但是 dist 目录下却没有任何东西,因为我们之前提到了,开发服务器打包的文件是写入内存中的,不是硬盘里。

为了方便我们看效果,我们在 package.json 中的 scripts 中在增加一行 "dev-build": "webpack --mode development" 用来打包我们开发环境的代码。

运行 npm run dev-build 脚本,完成之后可以发现 dist 目录已经打包出来了 index.html 文件 和 main.[hash].js 文件,打开 index.html 文件可以发现标签的双引号已经没去掉了,并且引入的脚本也自动加上了 ?[hash] 值。

<!DOCTYPE html>
<html lang=en>
<head>
    <meta charset=UTF-8>
    <meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
    <title>Title</title>
</head>
<body>
    <div id=app>webpack</div>
<script type=text/javascript src=main.e6570abab6c2814d1608.js?e6570abab6c2814d1608></script></body>
</html>

预览 index.html 文件,一切也都正常。

passwebpack4以后,如果你看到终端输入有下面这么怪异的一行,

而正巧你又是一个 强迫症患者,在 webpack.config.js 中添加 stats: { children: false } 即可。

支持图片

前端项目中肯定少不了图片资源,如果我们直接在 css 或者 js 文件中引入图片资源去打包,那么肯定是通不过了,因为 webpack 无法识别图片资源,因为图片也不是一个有效的模块。

此时,我们需要引入这个两个 loader 去解决它, file-loader url-loaderfile-loader 解决CSS等文件中的引入图片路径问题,url-loader 当图片小于limit的时候会把图片base64编码,大于limit参数的时候还是使用file-loader 进行拷贝

//index.js
import Avatar from './images/avatar.jpg'

let img = new Image();

img.src = Avatar;

document.body.appendChild(img);
//index.css

body{
    background-color: #f2f2f2;
    background-image: url("../images/scene.jpg");
    background-repeat: no-repeat;
}
{
    test:/\.(png|jpg|gif|svg|bmp)$/,
    use:{
        loader: 'url-loader',
        options: {
            limit: 10 * 1024,
            outputPath: 'images/'
        }
    }
}

options 中的 limit 就是图片的限制,这里我指定的是 10kb ,小于 10kb 的图片会以 base64 编码输出,outputPath 指定了拷贝文件输出目录,默认是dist目录下。

现在我们 npm run dev-build 走一波~

也不报错,运行 index.html 文件,也很正常!

编译less 和 sass

现在开发过程中,为了简化我们书写 css 的过程,我们一般项目中引入了 lesssass 这样的预加载器~

同样的你不处理之前 webpack 是不认识 lesssass 文件的,毕竟它不是一个 js 模块,我们通过需要借助 less-loadersass-loader 来处理这些文件~

{
    test: /\.less/,
    use: ['style-loader', 'css-loader', 'less-loader']
}, {
    test: /\.scss/,
    use: ['style-loader', 'css-loader', 'sass-loader']
}

这样,你就可以放心在你的项目里引入 css 预处理了~

分离css

因为CSS的下载和js可以并行,当一个HTML文件很大的时候,那么页面加载肯定会非常慢,那么我们希望可以把CSS单独提取出来加载,为每个包含 CSSJS 文件创建一个 CSS 文件,按需加载。

我们需要 mini-css-extract-plugin 这么一个插件

new MiniCssExtractPlugin({
    filename: 'css/[name].[hash].css',
    chunkFilename: "css/[id].css"
})

除此之外,我们的还需要做一个操作就是将我们之前处理cssstyle-loader 改写成下面这种形式:

{
    test: /\.css$/,
    use: [{
        loader: MiniCssExtractPlugin.loader
    }, 'css-loader']
},{
    test: /\.less/,
    use: [{
        loader: MiniCssExtractPlugin.loader
    }, 'css-loader', 'less-loader']
}, {
    test: /\.scss/,
    use: [{
        loader: MiniCssExtractPlugin.loader
    }, 'css-loader', 'sass-loader']
}

趁热打铁,赶紧来试试,npm run dev-build 进行打包~

预览 index.html 页面,打开控制台network可以发现下载文件的时候多出了一个 main.css 文件,并且我们 htmlhead 头,已经换成了 link 方式,并且你的 dist 目录下会多出一个 css 的文件夹,里面存放打包后的 css 文件。

tips: 如果你在外部的css 文件中文件中引入图片,而图片放在了images目录下,那么打包上线后的图片会出现 404 的情况,你可以查看打包后的 css 文件,就可以反向是路径的问题,需要配置一下 publicPath 即可~

{
    loader: MiniCssExtractPlugin.loader,
    options: {
        publicPath: '/'
    }
}

注意,这个一般是在 服务器 上会出现,本地打包后没有起服务也是看不到的。

处理CSS3属性前缀

我们在书写 css 的时候,为了浏览器的兼容性,有时候我们必须加入-webkit-ms-o-moz这些浏览器前缀,但我们又不想去书写这些繁琐的东西,这时候可以交给我们 postcss 来处理~

postcss的主要功能只有两个:

我们首先通过 npm install postcss-loader autoprefixer -D 来安装依赖

想要这个 postcss 正常工作,我们需要配置两个东西

module.exports={
    plugins:[require('autoprefixer')]
}
{
    test: /\.css$/,
    use: [{
        loader: MiniCssExtractPlugin.loader
    }, 'css-loader', 'postcss-loader'],
    include: path.join(__dirname, './src'),
    exclude: /node_modules/
}

这里我在我的 index.css 中添加了如下测试代码:

//index.css
::placeholder {
    color: orange;
}

并在我的 index.js 中创建了一个 input 控件:

//index.js

let input = document.createElement('input');
document.body.appendChild(input);

运行 npm run dev-build 打包,预览 index.html ,打开调试工具的的 sources板块,查看 main.css , 发现对应的前缀已添加,并且我页面的 input 已经变成了 orange~

转义ES6/ES7

虽然 es6es7 的代码我们已经或多或少都在项目中,但是大部分浏览器对于 es6es7 代码的支持度并不高,很大部分的浏览器还是只能解析 es5 的代码,为了让我们能正常使用 es6es7的代码,我们需要借助 webpack 对其进行转义,转成 es5 代码。

我们需要借助 babel 这个工具,它是一个编译JavaScript的平台,可以把ES6/ES7的代码转义为ES5代码。

{
    test: /\.js$/,
    loader : 'babel-loader' ,
    exclude:'/node_modules/'
}
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "browsers": [
            "> 1%",
            "last 2 versions"
          ]
        },
        "debug":true //调试使用
      }
    ]
  ]
}
const test = (n)=> {
    return new Promise(function (resolve) {
        setTimeout(()=>{
            resolve([1,2,3,4].map(v=>v * v ))
        },n*1000)
    }).then(res=>{
        console.log(res);
    })
}

console.log(test)

查看打包后的文件,发现转换的不够彻底,只能针对语法进行了转换,对于 Promisemap 这些高级用法并没有被转换,这肯定是不行的,我们还要想办法把这些新的特性,兼容到低版本的浏览器里。

我们还需要 babel 提供的另一个工具—— polyfill

这是由于为了兼容低版本浏览器,polyfill 里面添加了很多辅助代码来帮助实现比如 Promisemap 这些新特性,默认情况下会被添加到每一个需要它的文件中,并且会全局注入,造成全局污染,如果我们在开发框架之类的,可能会发生冲突。

我们加了 debug 可以在终端看到,确实是加入了不少插件:

为了解决这个问题,我们引入 @babel/runtime 这个模块,来避免重复导入的问题
如果是写第三方库或者框架

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        "targets": {
          "browsers": [
            "> 1%",
            "last 2 versions"
          ]
        },
        "debug":true //调试使用
      }
    ]
  ],
  "plugins": [
    [
      "@babel/plugin-transform-runtime",
      {
        "corejs": false,
        "helpers": true,
        "regenerator": true,
        "useESModules": true
      }
    ]
  ]
}

终端日志可以看到注入的辅助代码大幅度较少,文件的大小也大幅度减少,赞!

拷贝静态文件

我们有些项目中的文件,不是 js ,也不是 css , 图片之类的,比如 README.md这些静态资源,我也希望能打包到我的项目里,怎么办呢?

其实就是文件的拷贝操作,我们只需要将这些文件拷贝到目标目录下即可,我们可以利用 copy-webpack-plugin 这个插件

new CopyWebpackPlugin({
    from: path.resolve(__dirname,'src/static'),
    to:path.resolve(__dirname,'dist/static') 
})

from 是静态资源目录源地址,to是要拷贝文件的目标地址,so easy~

npm run dev-build 打包运行!

可以发现我们的 dist 目录已经将 src/static 的文件拷贝到了 dist/static

打包前清空

我们修改了文件之后,每次打包都会在 dist 目录下产生一个新的 main.[hash].js , 随着我们打包次数的增加,dist 目录下会生产出一堆的 main.[hash].js,不出意外,你的dist目录应该已经有不少了~

为了保证我们每次看到的都是最新的打包资源,而不受之前打包文件的干扰,这里我们需要引入另一个插件—— clean-webpack-plugin

plugins:[
    new CleanWebpackPlugin()
]

pass:该插件引入方式是稍微有点不同,通过以下这种方式引入:

const { CleanWebpackPlugin } = require('clean-webpack-plugin')

现在,每次打包前都会先清空 dist 目录下的文件,然后才产出打包后的文件,这样看起来就清晰多了!

服务器代理

我们在开发时,有时候希望在同域名下发送 API 请求 ,那么代理某些 URL 会很有用,代理 API 的配置对于 webpack来说,配置就非常简单了,只需要在 proxy 中添加代理规则即可

 proxy :  {
    "/api/test": {
     target: 'http://lohost:3000/', 
     secure: false,  
     changeOrigin: true, 
     pathRewrite: {
       '^/api/test': '/test'
    }
}

上面这行配置,就可以将 /api/test 开头的接口地址,会被代理到 http://localhost:3000/test 下,如果是https接口,需要配置 secure 这个参数 为 true ,如果接口跨域,需要配置changeOrigin这个参数为 true

压缩JS和CSS

我们现在打包出来的文件无论是 jscss 都是源文件,我们希望在打包的时候压缩我们的代码,一来是为了安全,二来是可以减少我们打包文件的体积。

我们选择使用terser-webpack-plugin来压缩js文件,替换掉之前的 uglifyjs-webpack-plugin,解决uglifyjs不支持es6语法问题,使用 optimize-css-assets-webpack-plugin 来压缩 css 文件

new TerserPlugin({
    parallel: true,
    cache: true
}),
new OptimizeCSSAssetsPlugin({
    assetNameRegExp:/\.css$/g,
    cssProcessor:require('cssnano')
})

TerserPlugin 中的 parallel 代表开启多进程,cache 代表设置缓存,OptimizeCSSAssetsPlugin 中加载了一个 cssnano 的东西, cssnanoPostCSSCSS优化和分解插件,会自动采用格式很好的CSS,并通过许多优化,以确保最终的生产环境尽可能小。

现在我们在继续打包一次!

查看我们打包后的文件,可以发现 jscss 文件都没打包成了一行,搞定!

结语

ok~ 看到这里,想必你对 webpack 已经有了一个比较完善的认识,对常见的一些配置打包的 loader 或者 plugin 都有一定的了解了,总结会发现,套路基本都差不多,需要什么 loaderplugin 只要去对应去查找即可~

emmm,其实 webpack 的功能非常强大,配置也是相当的多样化,这里只是列举了一些比较常见的功能,对你来说也只是一个抛砖引玉的作用,它的内部实现也是相当的复杂,想要真正弄懂 webpack ,还是需要多下一番苦功夫的~

对了,2019年还有几个小时就结束了~

2020年,加油~

上一篇下一篇

猜你喜欢

热点阅读