webpack4让一切变得简单

5 篇文章 0 订阅

目录

概念

安装

webpack.config配置文件

入口和输出

模式

loader

 插件(plugins)

tml-webpack-plugin

extract-text-webpack-plugin

webpack内置方法

开发

webapck-dev-server



概念

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

它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。

安装

//全局安装
npm install -g webpack
//安装到你的项目目录
npm install --save-dev webpack
//或者
yarn add webpack --dev

webpack4还需要安装webpack-cli

npm install --save-dev webpack-cli

现在从零开始搭建环境,运行

npm init

会生成一个package.json文件,有以下内容

{
  "name": "init",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

然后本地安装webpack,记得-dev,代表开发环境需要的依赖。接着我们在本地新建两个文件夹,js和views,js下存放main.js,views下存放index.html。

 现在我们通过webpack命令行来把main.js文件打包到views目录下,运行

webpack js/main.js views/index.js

 提示我们需要安装webpack-cli,webpack3中webpack-cli是在webpack包里面的,webpack4改成了单独的两个包,可以选择全局-g安装也可以选择本地安装。

这里我ctrl+c退出运行以下命令

npx webpack js/main.js -o views/index.js

 npx 会自动查找当前依赖包中的可执行文件,如果找不到,就会去 PATH 里找。如果依然找不到,就会帮你安装

-o 表示指定的输出文件位置

 这里有个黄色部分的内容提示,是提示我们没有设置模式mode,mode在后面会说到,这个提示可以不理也可以在命令后面加--mode=“development”。

运行完成我们打开views目录就能看到index.js文件,里面就是打包好的js代码。但是这样一个个打包非常的麻烦,接下来引出webpack的配置文件,默认命名webpack.config.js。

webpack.config配置文件

从 webpack v4.0.0 开始,可以不用引入一个配置文件。然而,webpack 仍然还是高度可配置的。在开始前你需要先理解四个核心概念

  • 入口(entry)
  • 输出(output)
  • loader
  • 插件(plugins)

 知道这四个东西,你就可以说懂webpack4了,但是还没到熟的地步。

入口和输出

入口(entry)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。

出口(output)告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist

在根目录新建webpack.config.js

module.exports={
    entry:"./js/main.js"
}

有了入口,还需要出口,指定打包好的文件输出位置。这里我们需要node的path模块

const path = require('path');
module.exports={
    entry:"./js/main.js",
    output: {
        path: path.resolve(__dirname, 'dist'),//当前目录下的dist文件夹
        filename: 'index.js'//输出的文件名
    }
}

path 模块提供用于处理文件路径和目录路径的实用工具。

resolve方法用于将绝对路径转为相对路径。

 运行npx webpack,当前目录就会生成dist文件夹,文件夹里面是我们的js文件。

有时候开发需要我们要生成不唯一的js文件,为了防止页面缓存,上图中有个Hash值,每打包一次,值都是不同的,我们可以用这个值来为js文件加个版本。

const path = require('path');
module.exports={
    entry:"./js/main.js",
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'index.[hash].js'
    }
}

 

 

我们发现,dist目录已经有我们想要的文件,但是每次打包,之前打包好的文件就不想要了,不然文件越积越多。这里就先留着,等到了插件再处理。

我们发现,运行的结果最后还是报mode未设置的警告。后面我们会通过模式mode解决这个警告。

入口entry还有另一种写法,多文件入口,配合输出一起使用。entry接收一个对象,每个属性对应文件路径。我们在js文件夹下面新建app.js。

const path = require('path');
module.exports={
    mode: 'development',
    entry: {
        index: './js/main.js',
        app: './js/app.js'
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[hash].js'
    }
}

[name]中的name对应的则是entry的属性名。

 

模式

mode分为开发模式development和生产模式production,如下配置

const path = require('path');
module.exports={
    mode: 'development',
    entry:"./js/main.js",
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'index.js'
    }
}

支持以下字符串值

选项描述
development会将 process.env.NODE_ENV 的值设为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin
production会将 process.env.NODE_ENV 的值设为 production。启用 FlagDependencyUsagePluginFlagIncludedChunksPluginModuleConcatenationPluginNoEmitOnErrorsPluginOccurrenceOrderPluginSideEffectsFlagPlugin 和 UglifyJsPlugin.

记住,只设置 NODE_ENV,则不会自动设置 mode。 

// webpack.development.config.js
module.exports = {
+ mode: 'development'
- plugins: [
-   new webpack.NamedModulesPlugin(),
-   new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") }),
- ]
}

再次打包文件

 现在警告已经没有了。

loader

loader 用于对模块的源代码进行转换,包括图片,字体文件,html文件,当然还有一些处理js语法的loader。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。

首先我们来说一下css的loader。

css的loader有很多,包括css-loader、stylus-loader、sass-loader等,不同的loader对应处理不同的css文件。

我们在根目录新建一个css文件夹并新建index.css文件。

在main.js引入css文件

接着下载css-loader和style-loader。

npm i --save-dev css-loader style-loader

style-loader是将css-loader打包好的css代码以<style>标签的形式插入到html文件中。

const path = require('path');
module.exports={
    mode: 'development',
    entry:"./js/main.js",
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'index.js'
    },
    module:{
        rules: [
            {
                test: /\.css$/,
                use: ["style-loader","css-loader"],
                include: path.resolve(__dirname, 'css'),
                exclude: path.resolve(__dirname, 'node_modules')
            },
        ]
    }
}

rules允许你在 webpack 配置中指定多个 loader。 这是展示 loader 的一种简明方式,并且有助于使代码变得简洁。同时让你对各个 loader 有个全局概览。

test 属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件,用正则匹配。

use 属性,表示进行转换时,应该使用哪个 loader,可以存放多个loader,执行循序是从右到左

include 只命中css目录里的css文件,加快webpack搜索速度

exclude 排除node_modules目录下的文件

 在webpack官网中有另一种写法

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

 loader 指定loader

options 参数配置

 浏览器打开我们发现css文件已经内嵌到页面,但是实际开发我们不想使用内嵌的css,我们想使用外联的css,通过link标签引入。这块也在后面的插件里一起讲。

我们还可以使用一些css的预处理语法,如sass,scss,stylus等等,下面就拿sass举例,在css文件夹下面新建一个index.sass文件。

 接着在main.js导入,在webpack里配置处理。

const path = require('path');
module.exports={
    mode: 'development',
    entry:"./js/main.js",
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'index.js'
    },
    module:{
        rules: [
            {
                test: /\.(c|sa|sc)ss$/,
                use: ["style-loader","css-loader","sass-loader"],
                include: path.resolve(__dirname, 'css'),
                // exclude: path.resolve(__dirname, 'node_modules')
            },
        ]
    }
}

再打包运行我们就会发现字体变大了。

 接下来我们讲图片的处理。新建img文件夹,放入bg.png。在index.css里引入。

 安装处理图片的file-loader。

npm i file-loader --save-dev

配置处理的规则。

const path = require('path');
module.exports={
    mode: 'development',
    entry:"./js/main.js",
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'index.js'
    },
    module:{
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: ["file-loader"],
            },
        ]
    }
}

 我们会发现在dist目录下生成了一个.png结尾的图片。但是这不是我们想要的结果,如果是一百张图片都和其他静态文件一起就会很乱,现在我们想要的是把打包好的图片文件放在dist下的img文件夹里面。

这些,我们在file-loader的npm网站上就能看到。所以图片打包的模块我们可以改成这样。

 {
    test: /\.(png|svg|jpg|gif)$/,
    use: [{
    loader:"file-loader",
    options:{
        publicPath:"img",
        name:"[name].[ext]",
        outputPath: './img',
     }
    }],
},

 publicPath 图片打包前的目录

name 图片打包好后的名字,其中[name]表示文件/资源​​的基名,默认是图片打包前.前面的字段,[ext]表示目标文件/资源​​的文件扩展名,默认是图片打包前的.后面的字段
outputPath 图片打包好后输出的文件目录

再次运行我们发现dist文件夹下面多出了一个img文件夹,里面正好是我们想要的图片。现在看感觉图片处理的好像可以了。但是,对于优化来说还是有不足的地方。图片多了表示请求的次数增加,不管是什么图片,都会去请求。有些很小的图片我们就可以把它转成base64格式。

在这里我们就需要安装另一个图片的处理loader,url-loader。url-loader类似于 file-loader,但如果文件小于字节限制,则可以返回DataURL。

npm install --save-dev url-loader
 {
    test: /\.(png|svg|jpg|gif)$/,
    use: [{
         loader:"url-loader",
         options:{
              publicPath:"img",
              name:"[name].[ext]",
              outputPath: './img',
              limit: 8192
              }
         }],
},
limit 指图片的大小,小于这个大小的都会打包成base64。大于的都会打包存在img文件夹下。

 插件(plugins)

插件是 webpack 的支柱功能。webpack 自身也是构建于,你在 webpack 配置中用到的相同的插件系统之上!

插件目的在于解决 loader 无法实现的其他事

  • html-webpack-plugin

现在我们可以慢慢填前面留下来的坑了。首先,我们发现之前打包的一直都是js,css,图片等资源,html文件并没有打包。现在我们就来先说一下打包html文件的插件。

npm install --save-dev html-webpack-plugin

我们需要导入这个包,然后构造出处理html文件的函数。

+const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports={
    mode: 'development',
    entry:"./js/main.js",
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'index.js'
    },
    module:{
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: [{
                    loader:"url-loader",
                    options:{
                        publicPath:"img",
                        name:"[name].[ext]",
                        outputPath: './img',
                        limit: 8192
                    }
                }],
            },
        ]
    },
+    plugins: [
+        new HtmlWebpackPlugin(),
+    ]
}

运行的结果是dist目录下存在index.html文件,并且用浏览器打开就能看到我们想要的页面效果。不过这个简单的配置只对index.html文件打包,它默认打包index.html文件。所以如果是多页面的打包我们还需要用到插件的参数。

名称类型默认描述
title{String}Webpack App用于生成的HTML文档的标题
filename{String}'index.html'要将HTML写入的文件。默认为index.html。您可以在这里指定一个子目录太(如:assets/admin.html
template{String}``webpack模板的相对或绝对路径。默认情况下,src/index.ejs如果它存在,它将使用。
templateParameters{Boolean|Object|Function}``允许覆盖模板中使用的参数
inject{Boolean|String}truetrue || 'head' || 'body' || false将所有资产注入给定templatetemplateContent。传递true'body'所有javascript资源将被放置在body元素的底部。'head'将脚本放在head元素中
favicon{String}``将给定的favicon路径添加到输出HTML
meta{Object}{}允许注入meta-tags。例如meta: {viewport: 'width=device-width, initial-scale=1, shrink-to-fit=no'}
base{Object|String|false}false注入base标签。例如base: "https://example.com/path/page.html
minify{Boolean|Object}true如果mode'production',否则false控制是否以及以何种方式缩小输出。
hash{Boolean}falsetruewebpack为所有包含的脚本和CSS文件附加唯一的编译哈希。这对缓存清除很有用
cache{Boolean}true仅在文件被更改时才发出文件
showErrors{Boolean}true错误详细信息将写入HTML页面
chunks{?}?允许您仅添加一些块(例如,仅添加单元测试块)
chunksSortMode{String|Function}auto允许控制在将块包含到HTML之前应如何对块进行排序。允许的值是'none' | 'auto' | 'dependency' | 'manual' | {Function}
excludeChunks{Array.<string>}``允许您跳过一些块(例如,不添加单元测试块)
xhtml{Boolean}false如果truelink标记渲染为自动关闭(符合XHTML)

所以我们可以这样配置。

 plugins: [
        new HtmlWebpackPlugin({
            template:"./views/index.html",
            title: 'index',
            filename: 'views/index.html',
            inject:true,
            hash:true
        })
 ]

这样写,我要是有三十个页面,那不是要复制三十份,代码就会冗余。我们可以封装成方法调用,即使是三十个页面也不过增加了三十多行代码。

const getHtmlConfig=(name,title)=>
    new HtmlWebpackPlugin({
        template:`./views/${name}.html`,
        title: title,
        filename: `views/${name}.html`,
        inject:true,
        hash:true
   })

title虽然可以动态传入,但是在html页面里面还需要插入指定代码,不然title还是默认打包前的title。

 <title><%= htmlWebpackPlugin.options.title%></title>

然后还有让webpack知道应该引入哪个js,css先不讲,因为css的单独打包还没有讲。

这里需要输入输出配合一起。

const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');

const getHtmlConfig=(name,title)=>
    new HtmlWebpackPlugin({
        template:`./views/${name}.html`,
        title: title,
        filename: `views/${name}.html`,
        inject:true,
        hash:true,
+        chunks: [name]
    })
module.exports={
    mode: 'development',
    entry:{
+        "index":"./js/main.js",
+        "news":"./js/main.js"
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'js/[name].js'
    },
    module:{
        rules: [
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader'
                ]
            },
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: [{
                    loader:"url-loader",
                    options:{
                        publicPath:"img",
                        name:"[name].[ext]",
                        outputPath: './img',
                        limit: 8192
                    }
                }],
            },
        ]
    },
    plugins: [
        getHtmlConfig("index","首页1"),
        getHtmlConfig("news","news")
    ]
}
chunks 用来识别导入的js文件

打开打包好后的news.html我们就会发现,body的尾部引入了news.js。

  • extract-text-webpack-plugin

现在来填css打包的坑,之前我们css都是内嵌在页面里的,现在我们需要单独打包css文件。

npm install --save-dev extract-text-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const path = require('path');

const getHtmlConfig=(name,title)=>
    new HtmlWebpackPlugin({
        template:`./views/${name}.html`,
        title: title,
        filename: `views/${name}.html`,
        inject:true,
        hash:true,
        chunks: [name]
    })
module.exports={
    mode: 'development',
    entry:{
        "index":"./js/main.js",
        "news":"./js/main.js"
    },
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'js/[name].js'
    },
    module:{
        rules: [
            {
                test: /\.css$/,
                use: ExtractTextPlugin.extract({
                    fallback: "style-loader",
                    use: "css-loader"
                })
            },
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: [{
                    loader:"url-loader",
                    options:{
                        publicPath:"img",
                        name:"[name].[ext]",
                        outputPath: './img',
                        limit: 8192
                    }
                }],
            },
        ]
    },
    plugins: [
+        new ExtractTextPlugin("css/[name].css"),
        getHtmlConfig("index","首页1"),
        getHtmlConfig("news","news")
    ]
}

这样就配置好了,然后就可以很愉快的看到想要的效果了?这样想的人那就是太天真了。我一直烦webpack的地方也是在这里。

意思是这个版本的这个方法已经被弃用,现在摆在你面前的就只有两种方法,要么你把插件升级到最新版,要么你换插件。但是我用的安装方法已经是最新的啊,不,还有可能有更新的,那就是别人的测试版。

npm i extract-text-webpack-plugin@next --save-dev

在package.json里面看到的版本是4.0.0-beta.0,再次打包就不会报错了,而且也能看到在head里面引入了我们想要的css。

第二种方法是换插件,mini-css-extract-plugin可以解决之前的问题。这里就不再演示,npm官网上会有你所有疑惑的答案。

  • webpack内置方法

那么多js代码,总会有一些公用的,那这些公用的方法在每个js文件都写一遍就会冗余。现在我们来把这些代码独立出来。

webpack4以前,打包独立js的办法是在插件引用。

plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name : 'common',
            filename : 'js/base.js'
        }),
]

 

但是再webpack4,这个方法已经移除,用的人就会很烦,又看到命令行运行报错,又必须去啃又臭又长的英文文档。

现在内置的方法不用再构造出来,直接和Plugins同级。

  optimization:{
        splitChunks:{
            cacheGroups: {
                commons: {
                    name : 'common',
                    chunks: "initial",
                    minChunks: 2
                }
            }
        }
    }
name 打包好后的名字
chunks 这表示将选择哪些块进行优化。当提供一个字符串,有效值为all,async和initial。提供all可以特别强大,因为这意味着即使在异步和非异步块之间也可以共享块。
minChunks 分割前必须共享模块的最小块数。

这样配置了还不行,你必须要让html页面也引用公共模块的js,在打包html的函数里面添加即可。

const getHtmlConfig=(name,title)=>
    new HtmlWebpackPlugin({
        template:`./views/${name}.html`,
        title: title,
        filename: `views/${name}.html`,
        inject:true,
        hash:true,
-       chunks: [name]
+       chunks: ["common",name]
    })

 打包完在html的body尾部就能看到引入的代码,js文件夹下也存在了common.js文件。

前面留了一个文件打包使用hash生成不同文件的坑,现在来填。文件打包后上次打包的文件会留下来,这样会叠加占用空间。这里我们用到一个插件clean-webpack-plugin。

npm install --save-dev clean-webpack-plugin

 

const CleanWebpackPlugin = require('clean-webpack-plugin');
plugins:
new CleanWebpackPlugin(),

 这样每次打包都会清dist文件夹。

开发

我们来会看一下前面说的那么多,在开发的时候这样做是不是有哪些可以改进的(在不增加其他需求的情况下)。

好像我们页面的头部是一样的,略有不同也是引入的css文件的不同,基本像之后加的mate都会差不多。那这样我们就可以把同样的部分提取出来。

这里我们使用到html-loader。

npm i html-loader --save-dev

在views文件夹下新建template文件夹,在template下新建head.string,这个后缀名随你喜欢,只要不要html结尾就行,因为用html结尾会和html打包的插件冲突。

然后我们把相同的头部提取到head,string。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">

是不是觉得很奇怪,title也相同,为什么不提取出来,我试了,动态设置title那一块并没有成功,也就是说打包好的页面title标签里的还是<%= htmlWebpackPlugin.options.title%>。

然后我们怎么导入到index.html呢。

webpack4以前我是用<%= require('./template/head.string')%>,然后我看来一下文档es6的方法也是可以的

${require('./template/head.string')}。所以两种方法都能用。

而如果我们要在页面中使用图片就不能直接在img的src里这样写../img/bg.png,我们需要像引用页面那样

<img src="${require(`../img/bg.png`)}" alt="">

接下来我们开始提需求,如果我有两个显示屏,一个看我写的代码,一个看页面的效果,当我代码改变了,就要用鼠标移到展示网页的那块显示屏,点击刷新。这样我觉得好麻烦,有没有什么办法,我代码一改页面就帮我自动刷新呢。这就用到了一个很好用的webpack热更新方法。还要配合webapck-dev-server一起使用。

  • webapck-dev-server

npm install webpack-dev-server --save-dev

所以我们开启文件的package文件也要修改。

"dev": "webpack-dev-server",

然后我们要在配置文件配置端口,在输入输出同级。

  devServer: {
        contentBase: path.join(__dirname, 'dist'),
        compress: true,
        port: 9000
    },

contentBa 告诉服务器从哪里提供内容。只有在您想要提供静态文件时才需要这样做。

compress 为所服务的一切启用gzip压缩

port 指定用于侦听请求的端口号。

当然devSerner有个很牛逼的功能,类似Nginx的映射。可以通过配置proxy,把当前请求映射到指定的端口,并且可以映射多个。

 

devServer: {
    proxy: {
      '/api': 'http://localhost:3000'
    }
  }

配置好我们就可以运行看效果,直接运行dev。代码还是原先配置图片的代码,但是我们发现,背景图片并没有显示出来,打开调试看到的是和我们预期不同的地址。原本图片的地址是http://localhost:9000/img/bg.png,然而实际显示的是http://localhost:9000/css/img/bg.png。多了点东西。

我去网上查看了别人的解决方法都说这样行那样行,但是我自己来试就是不行。想了很久会不会是css打包的beat版本有问题。所以我考虑换成另一种打包css的方法mini-css-extract-plugin。

npm install --save-dev mini-css-extract-plugin
 {
      test: /\.css$/,
      use: [
          {
              loader: MiniCssExtractPlugin.loader,
              options: {
                  publicPath: '../',
                  hmr: process.env.NODE_ENV === 'development',
                  },
              },
      'css-loader',
       ],
},

插件换成这样。

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

然后再运行,用http://localhost:9000/打开页面发现图片显示正常,地址也正常。

我们再试一下热刷新功能。把字体变大变色。 

 不用刷新,效果已经出来了,至此基本的webpack4的功能配置就说的差不多了。不过还有一个是开发环境和生产环境的webpack配置,留着下次讲。

demo已放GitHub,可以clone使用。webpack-demo

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值