Webpack 4 学习之路(上)

Webpack simple

以下均在 Webpack 4.42.1 版本环境下

mkdir webpack-simple
cd webpack-simple
npm init
npm i --save-dev webpack webpack-cli
touch index.html
touch webpack.config.js
mkdir src
cd src
touch main.js

在 index.html 中引入 bundle.js

<script src="./dist/bundle.js"></script>

在 main.js 中写上如下代码

console.info('hello webpack');

webpack.config.js 如下

const path = require('path'); // node path 模块用来处理文件路径

module.exports = {
	entry: {
    	app: './src/main.js'
  	},
	output: {
		filename: 'bundle.js',
		path: path.resolve(__dirname, 'dist') // __dirname 为当前文件路径,dist 为绝对路径
	}
}

执行「webpack」命令,在根目录「dist」文件夹下就会看到一个「bundle.js」文件
bundle.js
打开文件后你会发现代码量很多,实际上我们却只写了一行代码,这其中前面的代码都是 webpack 生成的「runtime」和「mainfest」代码,后面会讲到。

用浏览器打开 index.html,F12 能看到打印输出了 “hello webpack”

loaders

有 html 还不够,我们还得写样式呀,现在在 src 目录下新建 style.css 文件

body {
	background: pink;
}

在 main.js 中引入 style.css

import './style.css'
console.info('hello webpack')

现在我们再次执行「webpack」命令,会发现报下面的错误
在这里插入图片描述
这是因为「webpack」只能对「js」文件进行打包编译,碰到「css」文件它识别不出来,这个时候我们就要加上「loader」来让它识别「css」文件。

执行 “npm i --save-dev style-loader css-loader”

修改「webpack.config.js」文件如下

const path = require('path')

module.exports = {
  entry: {
    	app: './src/main.js'
  	},
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        // 匹配 css 文件
        test: /\.css$/,
        /*
        先使用 css-loader 处理,返回的结果交给 style-loader 处理。
        css-loader 将 css 内容存为 js 字符串,并且把 background,@font-face 等引用的图片,
        字体文件交给指定的 loader 打包
        */
        use: ['style-loader', 'css-loader']
      }
    ]
  }
}

再次执行「webpack」命令,就不会报错了,打开「index.html」就能看到添加样式成功了。引入的图片、字体、html等,都有相应的 loader 来编译。

rules: [
    {
      /*
      使用 babel 编译 ES6 / ES7 / ES8 为 ES5 代码
      使用正则表达式匹配后缀名为 .js 的文件
      */
      test: /\.js$/,

      // 排除 node_modules 目录下的文件,npm 安装的包不需要编译
      exclude: /node_modules/,

      /*
      use 指定该文件的 loader,值可以是字符串或者数组。
      这里先使用 eslint-loader 处理,返回的结果交给 babel-loader 处理。loader 的处理顺序是从最后一个到第一个
      eslint-loader 用来检查代码,如果有错误,编译的时候会报错
      babel-loader 用来编译 js 文件
      */
      use: ['babel-loader', 'eslint-loader']
    },
    {
      // 匹配 html 文件
      test: /\.html$/,
      /*
      使用 html-loader,将 html 内容存为 js 字符串,比如当遇到
      import htmlString from './template.html';
      template.html 的文件内容会被转成一个 js 字符串,合并到 js 文件里
      */
      use: 'html-loader'
    },
    {
      // 匹配 css 文件
      test: /\.css$/,
      /*
      先使用 css-loader 处理,返回的结果交给 style-loader 处理。
      css-loader 将 css 内容存为 js 字符串,并且把 background,@font-face 等引用的图片,
      字体文件交给指定的 loader 打包,类似上面的 html-loader,用什么 loader 同样在 loaders 对象中定义,等会下面就会看到
      */
      use: ['style-loader', 'css-loader']
    },
    {
      /*
      匹配各种格式的图片和字体文件
      上面 html-loader 会把 html 中 <img> 标签的图片解析出来,文件名匹配到这里的 test 的正则表达式,
      css-loader 引用的图片和字体同样会匹配到这里的 test 条件
      */
      test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/,

      /*
      使用 url-loader,它接受一个 limit 参数,单位为字节(byte)
      当文件体积小于 limit 时,url-loader 把文件转为 Data URI 的格式内联到引用的地方
      当文件大于 limit 时,url-loader 会调用 file-loader,把文件储存到输出目录,并把引用的文件路径写成输出后的路径

      比如 views/foo/index.html 中
      <img src="smallpic.png">
      会被编译成
      <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAA...">

      而
      <img src="largepic.png">
      会被编译成
      <img src="/f78661bef717cf2cc2c2e5158f196384.png">
      */
      use: [
        {
          loader: 'url-loader',
          options: {
            limit: 10000
          }
        }
      ]
    }
  ]

plugins

这时候如果我们修改「webpack.config.js」中「output」的「filename」的名字,「index.html」中的名字也要修改,有没有办法在编译的时候,自动生成「index.html」并且自动引入「entry」文件?

「html-webpack-plugin」就是用来做这个的

安装

npm i --save-dev html-webpack-plugin

修改「webpack.config.js」

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

module.exports = {
  entry: {
    	app: './src/main.js'
  	},
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        // 匹配 css 文件
        test: /\.css$/,
        /*
        先使用 css-loader 处理,返回的结果交给 style-loader 处理。
        css-loader 将 css 内容存为 js 字符串,并且把 background,@font-face 等引用的图片,
        字体文件交给指定的 loader 打包
        */
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [
	new HtmlWebpackPlugin({
		/*
	    template 参数指定入口 html 文件路径,插件会把这个文件交给 webpack 去编译,
	    webpack 按照正常流程,找到 loaders 中 test 条件匹配的 loader 来编译,那么这里 html 就是匹配的 html-loader
	    html-loader 编译后产生的字符串,会由 html-webpack-plugin 储存为 html 文件到输出目录,默认文件名为 index.html
	    可以通过 filename 参数指定输出的文件名
	    html-webpack-plugin 也可以不指定 template 参数,它会使用默认的 html 模版
	    */
		template: './index.html'
	})
  ]
}

删除「index.html」中引入「bundle.js」的脚本,执行「webpack」命令,成功后可以看到在「dist」目录下多了一个「index.html」,并且已经引入了「bundle.js」,用浏览器打开的效果跟刚才是一样的。

开发

在「main.js」中打印一个错误日志,并重新运行「wenpack」编译,用浏览器打开「index.html」

import './style.css'
console.info('hello webpack')
console.error('这是一个错误')

在这里插入图片描述
我们看到,浏览器只会提示我们「bundle.js」中有错误,但是定位不到原始文件的位置,这个时候我们要使用「source map」,在「webpack.config.js」中增加配置

module.exports = {
  entry: {
    	app: './src/main.js'
  	},
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  devtool: 'inline-source-map',
  ...
}

重现编译,刷新浏览器,我们就能看到精确定位代码位置
在这里插入图片描述
当我们在「main.js」中加入包含「ES6」语法的代码时,运行「npm run build」编译时并没有报错

function printArr(){
  const arr = Array(5).fill('6');
  arr.map(item => {
    console.log(item)
  })
};
printArr();

这是因为 webpack 4 依赖包中使用「terser-webpack-plugin」替代了之前一直使用的「uglifyjs-webpack-plugin」作为它的内置插件,并做代码压缩处理.

重新加载(live reloading)

每次要编译代码时,手动运行 webpack 就会变得很麻烦。
「webpack-dev-server」 提供了一个简单的 web 服务器,并且能够实时重新加载(live reloading)。

npm i --save-dev webpack-dev-server

webpack.config.js 加入配置

module.exports = {
	devServer: {
		contentBase: './dist'
	}
}

以上配置告知 webpack-dev-server,在 localhost:8080 下建立服务,将 dist 目录下的文件,作为可访问文件。

让我们添加一个 script 脚本,可以直接运行开发服务器(dev server):

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

现在执行「npm run dev」,浏览器会自动打开页面,修改「main.js」的内容

console.log('hello webpack-dev-server')

我们可以看到浏览器上的内容已经实时更新了,并不用再刷新浏览器来查看结果

HMR(模块热替换)

模块热替换(Hot Module Replacement 或 HMR)是 webpack 提供的最有用的功能之一。它允许在运行时更新各种模块,而无需进行完全刷新。

启用此功能实际上相当简单。而我们要做的,就是更新 webpack-dev-server 的配置,和使用 webpack 内置的 HMR 插件。

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

module.exports = {
  entry: {
    	app: './src/main.js'
  	},
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  devtool: 'inline-source-map',
  devServer: {
	    contentBase: './dist',
	    hot: true // +
	},
  module: {
    rules: [
      {
        // 匹配 css 文件
        test: /\.css$/,
        /*
        先使用 css-loader 处理,返回的结果交给 style-loader 处理。
        css-loader 将 css 内容存为 js 字符串,并且把 background,@font-face 等引用的图片,
        字体文件交给指定的 loader 打包,类似上面的 html-loader,用什么 loader 同样在 loaders 对象中定义,等会下面就会看到
        */
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './index.html'
    }),
    new webpack.NamedModulesPlugin(), // +
    new webpack.HotModuleReplacementPlugin() // +
  ]
}

分离配置

新建三个文件「webpack.base.conf.js」「webpack.dev.conf.js」「webpack.prod.conf.js」

// webpack.base.conf.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack');

module.exports = {
  entry: {
    	app: './src/main.js'
  	},
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        // 匹配 css 文件
        test: /\.css$/,
        /*
        先使用 css-loader 处理,返回的结果交给 style-loader 处理。
        css-loader 将 css 内容存为 js 字符串,并且把 background,@font-face 等引用的图片,
        字体文件交给指定的 loader 打包,类似上面的 html-loader,用什么 loader 同样在 loaders 对象中定义,等会下面就会看到
        */
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './index.html'
    })
  ]
}
// webpack.dev.conf.js
const webpack = require('webpack')
const merge = require('webpack-merge'); // 合并配置
const common = require('./webpack.base.conf');

module.exports = merge(common,{
  mode: 'development',
  devtool: 'inline-source-map',
  devServer: {
    contentBase: './dist'
  }
})
// webpack.prod.conf.js
const webpack = require('webpack');
const merge = require('webpack-merge');
const common = require('./webpack.base.conf');

module.exports = merge(common,{
  mode: 'production',
})
// package.json
"scripts": {
   "start": "webpack-dev-server --inline --progress --config webpack.dev.conf.js",
   "build": "webpack --config webpack.prod.conf.js"
 }

代码分离

先引入「lodash」

npm i --save lodash

main.js 中添加如下代码, 并执行「npm run build」命令

function component() {
  var element = document.createElement('div')

  element.innerHTML = _.join(['hello', 'webpack'], '')

  return element
}

document.body.appendChild(component())

在「dist」目录下,我们能看到「app.bundle.js」和「index.html」两个文件,但是打开「bundle.js」会发现我们「main.js」中引入的「lodash」也打包进去了,这样在后续引入多个第三方包后会使得我们「app.bundle」文件越来越大,所以我们要把第三方插件抽离出来,统一打包到一个文件中去

1、第三方插件分离

module.exports = {
	optimization: {
    splitChunks: { // 替代 webpack.optimize.CommonsChunkPlugin
      cacheGroups: {
        vendors: {
          chunks: "all",
          test: /[\\/]node_modules[\\/]/,
          name: "vendors",
          minChunks: 1,
          minSize: 30000,
          priority: 10 // 设置优先级
        }
      }
    }
  }
}

此时在「dist」目录下会多一个「vendors.cd5da2e8640d819f8b41.js」文件,打开就会发现里面是「lodash」的代码

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章