1.webpack应用实例

1.1.快速上手

初始化项目

mkdir webpack-demo
cd webpack-demo
npm init -y

安装webpack

npm i webpack@4.41.0 webpack-cli@3.3.9 -D

零配置使用webpack,webpack约束源文件目录必须为src, 默认配置文件为 src/index.js

我们新建目录如下:

webpack-demo
├── node_modules
├── src
|   ├── index.js
|   └── module1.js
├── package.json
├── package-lock.json

index.js

const fn1 = require("./module1")
fn1()

module1.js

function fn1() {
  alert(1)
}
module.exports = fn1

运行

npx webpack

注意: npx是npm的一个包运行器,是npm 5.2版本后提供的一个工具,它会自动找到项目下 node_modules/.bin下的相关命令来运行

接下来,我们将运行命令配置到package.json

"scripts": {
    "dev": "webpack --mode development"
},

配置完成后,我们就可以使用npm run dev来执行webpack命令了,注意: --mode是webpack的模式,有开发模式(development)和生产模式(production)

1.2.自定义配置文件

在前面我们使用的是webpack4的零配置运行的项目,如果我们需要实现更多的功能,我们需要自定义配置,比如:我们可以更改入口文件名、可以使用各种loader、插件等,webpack的配置文件名字通常是固定的,即webpack.config.js,配置都往这个文件里面写,因此,我们需要在项目根目录下创建好这个文件

// 引入nodejs的path模块来处理路径
const path = require("path")
module.exports = {
  // 模式配置
  mode: "development",
  // 入口文件  通常使用绝对路径
  entry: path.resolve(__dirname, "./src/index.js"),
  // 出口配置
  output: {
    filename: "app.js",
    path: path.resolve(__dirname, "dist")
  }
}

上面已经在配置文件里设置了模式,因此,在package.json中就不需要传入mode这个参数了

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack"
  },

1.3.环境变量

一个项目从开发到上线面临不同的环境,在开发阶段我们通常称为开发环境,部署到线上的环境我们通常称为生产环境,环境的不同,配置也是不一样的,因此,我们有必要来区分环境:

1.注入环境变量

const path = require('path')
const webpack = require('webpack')

module.exports = {
  mode: 'development',
  entry: path.resolve(__dirname, 'src', 'app.js'),
  output: {
    filename: 'app.js',
    path: path.resolve(__dirname, 'dist')
  },
  plugins: [
    new webpack.DefinePlugin({
      ENV: JSON.stringify('prod')
    })
  ] 
}

注入变量后,我们就可以在各个模块中使用环境变量了,例如,在app.js中去使用

console.log(ENV)

if (ENV === 'dev') {
  console.log('发送请求:http://localhost:8080')
}

if (ENV === 'prod') {
  console.log('发送请求:http://nodeing.com')
}

2.使用cross-env设置环境变量

npm install --save-dev cross-env

在package.json中使用

"scripts": {
    "build": "cross-env NODE_ENV=production webpack --config config/webpack.config.js"
}

1.4.拆分配置文件

我们可以将配置拆分到单独的文件中去方便管理,拆分后目录结构如下:

webpack-demo
├── config
|   ├── webpack.base.js 公共配置文件
|   ├── webpack.dev.js  开发环境配置
|   └── webpack.prod.js  生产环境配置
├── node_modules
├── src
|   ├── index.js
|   └── module1.js
├── package.json
├── package-lock.json

安装webpack-merge插件来合并配置项

npm i webpack-merge@4.2.2 -D

webpack.base.js

const path = require('path')
const webpack = require('webpack')

module.exports = {
  entry: path.resolve(__dirname, '../', 'src', 'app.js'),
  output: {
    filename: 'app.js',
    path: path.resolve(__dirname, '../', 'dist')
  },
  plugins: [
    new webpack.DefinePlugin({
      ENV: JSON.stringify('prod')
    })
  ]
}

webpack.dev.js

const base = require('./webpack.base')
const { smart } = require('webpack-merge')
module.exports = smart(base, {
  mode: 'development'
})

webpack.prod.js

const base = require('./webpack.base')
const { smart } = require('webpack-merge')
module.exports = smart(base, {
  mode: 'production'
})

1.5.webpack-dev-server

webpack-dev-server是我们在开发阶段需要用到的一个服务器,它会把代码打包到内存,我们可以通过http的方式访问到打包到内存的代码

安装

npm i webpack-dev-server@3.8.1 -D

修改package.json的启动命令

  "dev": "webpack-dev-server --config ./config/webpack.dev.js",

增加相关配置

webpack.dev.js

const base = require('./webpack.base')
const { smart } = require('webpack-merge')
const path = require('path')
module.exports = smart(base, {
  mode: 'development',
  devServer: {
    // 默认访问的根路径,webpack-dev-server会去这个路径下找到index.html文件并返回
    contentBase: path.join(__dirname, '../dist'),
    // 开启gzip压缩,增加文件返回速度
    compress: true,
    port: 8000,
    // 启动后自动打开浏览器
    open: true
  }
})

1.6.自动创建html文件

在前面webpack-dev-server中,我们设置的启动访问路径是项目下的dist目录,我们需要在这个dist目录下手动去创建一个index.html文件,并且将打包后的js文件手动添加到index.html文件中,这个时候,我们通过浏览器访问才能看到效果,在开发中,能用工具做的事情我们尽量用工具帮我们完成,我们可以使用 html-webpack-plugin 这个插件来帮我们做这件事情,它的作用就是根据模版文件生成一个静态文件,然后自动将打包好的js插入到这个 html文件中

首先,安装这个插件

npm i html-webpack-plugin@3.2.0 -D

webpack.dev.js 增加相关配置


const base = require('./webpack.base')
const { smart } = require('webpack-merge')
const path = require('path')
const htmlWebpackPlugin = require('html-webpack-plugin')
module.exports = smart(base, {
  mode: 'development',
  devServer: {
    // 默认访问的根路径,webpack-dev-server会去这个路径下找到index.html文件并返回
    contentBase: path.join(__dirname, '../dist'),
    // 开启gzip压缩,增加文件返回速度
    compress: true,
    port: 8000,
    // 启动后自动打开浏览器
    open: true
  },
  plugins: [
    new htmlWebpackPlugin({
      // 设置标题
      title: '螺钉课堂',
      // 设置模版路径
      template: path.resolve(__dirname, '../public/index.html'),
      // 设置打包的文件名
      filename: 'index.html',
      // 最小输出配置
      minify: {
        // 折叠空格
        collapseWhitespace: true
      }
    })
  ]
})

注意,如果要使用配置中的title,模版中需要使用这种方式来获取title值

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8"/>
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
  </body>
</html>

1.7.自动清除上次打包的文件

当我们执行打包操作,自动生成一个打包文件,如果前面有已经生成的文件,我们通常会手动去删除前一次已经生成的文件,以避免产生混乱,这个时候可以使用clean-webpack-plugin来帮我们完成这个操作

安装

npm i clean-webpack-plugin@3.0.0 -D

webpack.base.js 增加相关配置

const path = require('path')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
  entry: path.resolve(__dirname, '../', 'src', 'app.js'),
  output: {
    filename: 'app.js',
    path: path.resolve(__dirname, '../', 'dist')
  },
  plugins: [
    new webpack.DefinePlugin({
      ENV: JSON.stringify('prod')
    }),
    // 每次打包之前,先清除dist目录下的文件
    new CleanWebpackPlugin()
  ]
}

1.8.解析css文件

解析css文件需要两个loader,css-loader和style-loader,css-loader的作用就是解析css的语法,解析完成后传给style-loader, style-loader就会生成style标签,然后把这些css代码插入到html文件中,这样在html就会有样式了

npm i css-loader@3.2.0 style-loader@1.0.0 -D

webpack.base.js 配置文件中增加配置项目

const path = require('path')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
  entry: path.resolve(__dirname, '../', 'src', 'app.js'),
  output: {
    filename: 'app.js',
    path: path.resolve(__dirname, '../', 'dist')
  },
  plugins: [
    new webpack.DefinePlugin({
      ENV: JSON.stringify('prod')
    }),
    // 每次打包之前,先清除dist目录下的文件
    new CleanWebpackPlugin()
  ],
  // 每个文件都称为模块,module就是用来配置解析各种文件模块的,module里面主要涉及到几个问题,
  //  1 哪些模块文件需要被转换? 2 用什么loader去转? 3 用什么规则去转?
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              // css文件解析后,需要用后面的1个loader来处理,如果后面有多个loader,需要更改一下importLoaders的数量
              importLoaders: 1
            }
          },
          // "css-loader",
          'postcss-loader'
        ]
      }
    ]
  }
}


注意: loader的写法有三种: 1、字符串 2 数组 3、对象, 前面我们使用的就是字符串的形式

实际上,在没有对loader进行参数配置的时候,多个loader 我们更倾向于第二种写法,给loader传一个数组

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

注意: css-loader和style-loader是需要注意使用顺序的,如果配置成字符串的形式,css-loader要放在下面,如果配置成数组的形式,css-loader要放在前面,顺序不正确会报错

1.9.自动给css添加前缀

在写css的时候,某些属性我们可能会增加浏览器前缀,这个功能我们可以使用postcss-loader来帮我们实现,其中postcss-loader需要一个autoprefix插件来实现自动添加前缀

npm i postcss-loader@3.0.0  autoprefixer@9.6.1 -D

在项目文件夹下,新建一个postcss.config.js来给postcss-loader增加配置

postcss.config.js

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

接下来,需要在项目下,新建一个.browserslistrc文件,配置相关浏览器支持情况

// 项目要覆盖95%以上浏览器
cover 95%

最后,在webpack.base.js中配置好css文件的处理loader

{
  test: /\.css$/,
  use: [
    "style-loader",
    {
      loader: "css-loader",
      options: {
        // css文件解析后,需要用后面的1个loader来处理,如果后面有多个loader,需要更改一下importLoaders的数量
        importLoaders: 1
      }
    },
    // "css-loader",
    "postcss-loader"
  ]
},

1.10.生产环境单独抽离css文件

前面我们用style-loader把打包的css代码都插入到html的style标签里,如果我们想把css文件单独打包到某个文件中,再通过外联的样式来使用,这种需求,我们需要通过mini-css-extract-plugin插件来实现

npm i mini-css-extract-plugin@0.8.0  -D

增加相关配置:

webpack.prod.js

const dev = require("../build/webpack.dev")
const prod = require("../build/webpack.prod")
const path = require("path")
const merge = require("webpack-merge")
const htmlWebpackPlugin = require("html-webpack-plugin")
const { CleanWebpackPlugin } = require("clean-webpack-plugin")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
module.exports = function(env) {
  const isDev = env.development
  const base = {
    entry: path.resolve(__dirname, "../src/index.js"),
    output: {
      filename: "index.js",
      path: path.resolve(__dirname, "../dist")
    },
    // 每个文件都称为模块,module就是用来配置解析各种文件模块的,module里面主要涉及到几个问题,1 哪些模块文件需要被转换? 2 用什么loader去转? 3 用什么规则去转?
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [
            // 如果是开发环境,使用style-loader 不是开发环境用MiniCssExtractPlugin的loader
            isDev ? "style-loader" : MiniCssExtractPlugin.loader,
            {
              loader: "css-loader",
              options: {
                // css文件解析后,需要用后面的1个loader来处理,如果后面有多个loader,需要更改一下importLoaders的数量
                importLoaders: 1
              }
            },
            // "css-loader",
            "postcss-loader"
          ]
        },
        {
          test: /\.scss$/,
          use: ["style-loader", "css-loader", "sass-loader"]
        }
      ]
    },
    plugins: [
      // 如果是开发环境,就不使用样式抽离的插件
      !isDev &&
        new MiniCssExtractPlugin({
          // 配置一下抽离出来的css文件名
          filename: "css/main.css"
        }),
      new htmlWebpackPlugin({
        title: "螺钉课堂",
        template: path.resolve(__dirname, "../public/index.html"),
        filename: "index.html",
        minify: {
          collapseWhitespace: isDev ? false : true
        }
      }),
      // 每次打包之前,先清除dist目录下的文件
      new CleanWebpackPlugin()
    ].filter(Boolean)
  }
  if (isDev) {
    return merge(base, dev)
  } else {
    return merge(base, prod)
  }
}

1.11.压缩css

默认情况下,webpack只能压缩js而不能压缩css,如果想压缩css,需要单独使用插件来完成

安装插件

npm i optimize-css-assets-webpack-plugin@5.0.3 -D

在生产环境下我们才要压缩css,因此,我们需要将配置放到webpack.prod.js

const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin")
module.exports = {
  mode: "production",
  // 配置优化项目
  optimization: {
    // 配置压缩方案
    minimizer: [
      // 配置css压缩方案,注意,css手动选择压缩方式后,webpack就不会帮助压缩js文件了,也需要手动配置
      new OptimizeCSSAssetsPlugin({})
    ]
  }
}

安装了这个插件后,js压缩失效,因此需要单独安装js压缩插件

npm i terser-webpack-plugin@2.1.0 -D

webpack.prod.js配置

const TerserJSPlugin = require("terser-webpack-plugin")
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin")
module.exports = {
  mode: "production",
  // 配置优化项目
  optimization: {
    // 配置压缩方案
    minimizer: [
      new TerserJSPlugin({}),
      // 配置css压缩方案,注意,css手动选择压缩方式后,webpack就不会帮助压缩js文件了,也需要手动配置
      new OptimizeCSSAssetsPlugin({})
    ]
  }
}

1.12.预处理器文件处理

1.sass文件

sass这种css预处理器是以.scss结尾,需要用node-sass和sass-loader来处理

安装loader

npm i node-sass sass-loader -D

增加相关配置:

webapck.base.js

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

注意: 如果出现css文件中引入sass文件的情况,只用css-loader是不能解析的,必须加入sass-loader,并且需要在css-loader中声明,使用sass-loader才可以

举个例子: index.css 引入 a.css , a.css引入a.scss,这个时候需要增加配置才行

{
  test: /\.css$/,
  use: [
    "style-loader",
    // 需要把原来的字符串换成对象的形式,然后给css-loader配置参数
    {
      loader: "css-loader",
      options: {
        // 如果css文件引入其他文件(@import), 使用后面的1个loader来处理
        importLoaders: 2
      }
    },
    // "css-loader",
    "postcss-loader",
    "sass-loader"
  ]
},

2.less文件

3.stylus文件

1.13.解析图片

解析图片需要用到file-loader,它的作用是将图片拷贝到打包后到输出目录,并且返回一个可以引用的地址

安装file-loader

npm i file-loader@4.2.0 -D

增加图片支持配置

webpack.base.js

{
  test: /\.(jpe?g|png|gif|bmp)$/,
  use: "file-loader"
}

当图片比较小的时候,我希望把这个图片转成base64格式的字符串,这样的好处是少发http请求,完成这个转base64字符串需求需要用到url-loader, url-loader是在file-loader上做的一层封装, 我们需要将这个loader先安装上

npm i url-loader@2.1.0 -D

修改相关配置

{
  test: /\.(jpe?g|png|gif|bmp|svg)$/,
  use: {
    loader: "url-loader",
    options: {
      // 8k以下的图片转成base64
      limit: 8192,
      name: "img/[name].[ext]"
    }
  }
}

如果要支持字体图标,可以再新建一条匹配规则,使用file-loader来处理即可

相关配置

{
  test: /\.(eof|ttf|woff|woff2)$/,
  use: "file-loader"
}

1.14.解析js文件

ES的一些比较新的语法浏览器是不支持的,因此,我们需要将这些新的语法转成比较通用的语法,babel就是一个很好的转换工具,我们先安装需要的工具

npm i @babel/core@7.6.2  babel-loader@8.0.6 @babel/preset-env@7.6.2 -D

@babel/core是babel的核心,它转换语法的时候会用到 @babel/preset-env这个插件包, @babel/preset-env这些插件包里包含了各种新语法的转换功能,babel这个工具是可以独立运行的,如果想要和webpack结合,还需要安装一个babel-loader, babel-loader的作用就是把ES的一些新语法送到@babel/core,@babel/core再去调用@babel/preset-env插件来完成转换

增加配置

{
  test: /\.js$/,
  use: "babel-loader"
}

babel-loader 会去调一个babel的配置文件,我们需要在项目根目录下创建.babelrc的配置文件

{
  "presets": ["@babel/preset-env"]
}

如果使用了一些@babel/preset-env不能转换的语法,你还可以单独安装对应的转换插件来解决,例如:

class Person {
  name = "小红"
}

这种语法需要用 @babel/plugin-proposal-class-properties 这个插件来转换 安装插件

npm i @babel/plugin-proposal-class-properties@7.5.5 -D