前端工程化-Webpack 基础-打包 HTML、JS 和图片

以下皆为拉勾教育课件内笔记

打包 HTML

生成 HTML

html-webpack-plugin,该插件用来创建 HTML 文件,创建的 HTML 文件默认引⼊打包后的所有资源文件。复制 HTML 到打包目录、确保 HTML 中的路径正确(例如:HTML 标签中的 src )详情查看:https://www.npmjs.com/package/html-webpack-plugin

安装:

npm i html-webpack-plugin -D # webpack 5
npm i html-webpack-plugin@4 -D # webpack 4

引入:

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

配置:
在 webpack.config.js 的 plugins 插件中添加 HtmlWebpackPlugin

plugins: [ 
  new HtmlWebpackPlugin() // 添加这⼀⾏ 
]

执行命令:

webpack

设置 HTML模板

plugins: [ 
  // ⽤于⽣成 index.html 
  new HtmlWebpackPlugin({ 
    template: './src/index.html', // 指定 HTML 的模板 
  }) 
]

声明模板

<!DOCTYPE html> 
<html lang="en"> 
<head> 
  <meta charset="UTF-8"> 
  <meta name="viewport" content="<%= htmlWebpackPlugin.options.vi ewport %>"> 
  <meta http-equiv="X-UA-Compatible" content="ie=edge"> 
  <title>Demo</title> 
</head> 
<body> 
  <div class="container"> 
    <h1>Hello Webpack</h1> 
  </div> 
</body> 
</html>

设置 HTML 配置项

少量配置可以在 HtmlWebpackPlugin 中指定
大量配置建议指定 HTML 文件的模板

plugins: [ 
  // ⽤于⽣成 index.html 
  new HtmlWebpackPlugin({
    template: './src/index.html', // 指定 HTML 的模板 
    title: 'Webpack Plugin Sample', // 添加 HTML 的 title 
    meta: { // 添加 HTML 的 meta 
      viewport: 'width=device-width, initial-scale=1.0' 
    } 
  }), 
  // ⽤于⽣成 about.html 
  new HtmlWebpackPlugin({ 
    filename: 'about.html' // 指定声明的⽂件名,如果有多个模板,可以实例化多次 
  }) 
]

声明 HTML 模板(其中的变量比如 title 使用 EJS 语法)
EJS -- 嵌入式 JavaScript 模板引擎 | EJS 中文文档 (bootcss.com)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="<%= htmlWebpackPlugin.options.vi ewport %>"> 
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
  <div class="container">
    <h1><%= htmlWebpackPlugin.options.title %></h1>
  </div>
</body>
</html>

压缩 HTML

除了创建模板文件外,htmlWebpackPlugin 还可以通过配置,实现压缩 HTML 的效果,minify 选项可以配置压缩的内容。

plugins: [ 
  new CleanWebpackPlugin(), 
  new HtmlWebpackPlugin({
    template: "./src/index.html", 
    title: "Webpack Demo666",
    minify:{ 
      removeRedundantAttributes: true, // 删除多余的属性 
      collapseWhitespace: true, // 折叠空⽩区域 
      removeAttributeQuotes: true, // 移除属性的引号 
      removeComments: true, // 移除注释 
      collapseBooleanAttributes: true // 省略只有 boolean 值的属性值 例如:readonly checked 
    }
  }),
]

打包 JS

编译

babel
安装 babel-loader:npm install babel-loader @babel/core @babel/preset-env -D

@babel/core 包含 Babel 转换的核⼼ API
@babel/preset-env 包含最新 JS 语法的转换规则
babel-loader Webpack 中,转换 JS 的加载器

babel 结构:

添加配置:
启用 babel-loader 后 ES6+ 的语法会转成 ES5。但是 babel 只会对基本语法进行转移。像 promise 这样的高级语法,Babel 就不能转换。

const path = require('path') 
module.exports = {
  mode: 'none', 
  entry: './src/main.js', 
  output: { 
    filename: 'bundle.js', 
    path: path.join(__dirname, 'dist'), 
    publicPath: 'dist/' 
  }, 
  module: { 
    rules: [ 
      { 
        test: /\.m?js$/i, 
        exclude: /node_modules/, // 排除不需要打包的⽬录 
        use: { 
          loader: 'babel-loader', 
          options: { 
            presets: ['@babel/preset-env'] 
          } 
        } 
      } 
    ] 
  } 
}

polyfill
安装:npm i @babel/polyfill -D
使用:在入口文件中引入 @babel/polyfill

import '@babel/polyfill' 
// ......

@babel/polyfill 会对所有的 JS 新语法进行转译(没用到的新语法也会被转译),因此打包后的 JS 会 非常大

core-js
core-js 可以按需转译(即只转译用到的新语法)
安装:npm i core-js -D
使用:

const path = require('path') 
module.exports = {
  mode: 'none', 
  entry: './src/main.js', 
  output: { 
    filename: 'bundle.js', 
    path: path.join(__dirname, 'dist'), 
    publicPath: 'dist/' 
  }, 
  module: { 
    rules: [ 
      { 
        test: /\.m?js$/i, 
        exclude: /node_modules/, // 排除不需要打包的⽬录 
        use: { 
          loader: 'babel-loader', 
          options: { 
            // presets: ['@babel/preset-env'] 
            presets: [
              '@babel/preset-env',
              {
                // 按需加载
                useBuiltIns: 'usage',
                // 指定 core-js版本
                corejs: {
                  version: 3
                },
                // 指定兼容性做到哪个版本浏览器
                targets: {
                  chrome: '60',
                  firefox: '60', 
                  ie: '9', 
                  safari: '10', 
                  edge: '17'
                }
              }
            ]
          } 
        } 
      } 
    ] 
  } 
}

格式校验

之前进行格式校验使用的是 eslint-loader,但 eslint-loader 已经废弃。
现在推荐使用 eslint-webpack-plugin 来进行 JS 的格式校验
详情查看:https://www.npmjs.com/package/eslint-webpack-plugin

安装:npm i eslint eslint-config-airbnb-base eslint-webpack-plugin eslint-plugin-import -D

说明

使用:

const ESLintPlugin = require('eslint-webpack-plugin'); 
module.exports = { 
  // ... 
  plugins: [new ESLintPlugin(options)], 
  // ... 
};

设置配置项:

const ESLintPlugin = require('eslint-webpack-plugin'); 
module.exports = { 
  plugins: [ 
    # 使⽤ 配置项都可以在 eslint-webpack-plugin 的 github 上找到
    new ESLintPlugin({ 
      fix: true, 
      extensions: ['js', 'json', 'coffee'], 
      exclude: '/node_modules/'
    }), 
  ] 
};

指定 eslint 配置:

指定 eslint 配置的方式有多种

  • 在 package.json 中,通过 eslintConfig 指定
  • 在项目根目录下,通过 .eslintrc 指定(.eslintrc 可以通过 eslint --init 命令生成)
// package.json
"eslintConfig": { 
  "extends": "airbnb-base",
  #!!!!如果没使⽤ alias 下⾯的不⽤配置!!!!! 
  "settings": {
    "import/resolver": { 
      "webpack": { 
        "config": "webpack.config.js" 
      } 
    } 
  } 
}

打包图片

样式中的图片引入

打包图片时,最常用两个 loader

安装:npm install file-loader url-loader -D

配置:

const path = require('path'); 
const HtmlWebpackPlugin = require('html-webpack-plugin') 
module.exports = { 
  // 打包模式 
  mode: 'development', 
  // ⼊⼝⽂件
  entry: './src/index.js', 
  // 输出  
  output: { 
    path: path.resolve(__dirname, 'dist'), 
    filename: 'main.bundle.js', 
  }, 
  // loader 配置 
  module: { 
    rules: [ 
      { 
        test: /\.(png|gif|jpe?g)$/,
        use: {
          loader: 'url-loader', 
          options: { 
            limit: 2 * 1024, // 2 kb 设置图⽚⼤⼩,⼩于该数值的图⽚会被 转成 base64
            // 保留原图片名称及后缀名 
            name: "images/[name].[ext]"  // [name] 是原名称,[ext]是原后缀名
          } 
        }, 
      } 
    ] 
  },
  // plugin 配置
  plugins: [ 
    new HtmlWebpackPlugin({ 
      title: "Webpack Demo",
      template: "./src/index.html" 
    }) 
  ] 
};

上述配置可以处理样式文件中的图片引入,例如:

body { 
  /* background-color: #dbf; */ 
  background: url(./images/1.jpg) no-repeat; 
}

HTML 中的图片引入

html-loader 将 HTML 导出为字符串(负责引入 img,从而能被 url-loader 进行处理)
https://www.npmjs.com/package/html-loader

安装:npm install html-loader -D

配置:

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

module.exports = { 
  // 打包模式 
  mode: 'development',
  // ⼊⼝⽂件
  entry: './src/index.js', 
  // 输出 
  output: { 
    path: path.resolve(__dirname, 'dist'), 
    filename: 'main.bundle.js', 
  }, 
  // loader 配置 
  module: { 
    rules: [ 
      { 
        test: /\.(png|gif|jpe?g)$/, 
        use: { 
          loader: 'url-loader', 
          options: { 
            limit: 2 * 1024, // 2 kb
            name: "images/[name].[ext]", 
            // url-loader 默认采⽤ ES Module 进⾏解析,⽽ html-loader 引⼊图⽚使⽤的是 CommonJS 
            // 解决:关闭 url-loader 的 ES Module 解析,使⽤ CommonJS 进⾏解析 
            esModule: false
          } 
        }, 
      }, 
      { 
        test: /\.(htm|html)$/i, 
        // 负责处理 HTML 中的 img 图⽚ 
        loader: 'html-loader',
        options: { 
          // webpack4 中只需要在 url-loader 配置 esModule:false 
          // webpack5 需要 html-loader 也配置 esModule:false 
          esModule: false 
        } 
      } 
    ] 
  }, 
  // plugin 配置 
  plugins: [ 
    new HtmlWebpackPlugin({ 
      title: "Webpack Demo", 
      template: "./src/index.html"
    })
  ]
};

html-loader 与 html-webpack-plugin 冲突

问题:
入口页面中默认使用的 lodash 模板未被解析⽣效。 虽然 html-loader 可以解决 HTML 文件中的图片加载问题,但 HTML 中的 ejs 语法失效了。

原因:
htmlWebpackPlugin 会检查目标文件是否已经有 loader 处理,如果有其他 loader 处理, htmlWebpackPlugin 不再使用 lodash.template 去编译目标,直接返回 source。
这里,我们已经声明了通过 html-loader 来处理 html 文件,因此 ejs 语法失效

部分 htmlWebpackPlugin 的关键代码(位置:html-webpack-plugin/lib/loader.js)

const _ = require('lodash'); 

module.exports = function (source) { 
  // Get templating options 
  const options = this.getOptions(); 
  const force = options.force || false;
  const allLoadersButThisOne = this.loaders.filter((loader) => lo ader.normal !== module.exports); 

  // 如果有其他 loader 进⾏处理,则直接返回 
  if (allLoadersButThisOne.length > 0 && !force) { 
    return source; 
  } 

  // 否则使⽤ lodash.template 的模板进⾏语法解析 
  const template = _.template(source, { 
    interpolate: /<%=([\s\S]+?)%>/g, 
    variable: 'data', ...options 
  });

解决:
将模板文件统一为 ejs 格式,先通过 htmlWebpackPlugin 处理 ejs 文件,然后 html-loader 继续做后续处理。

  1. 修改模板⽂件后缀(src/index.html => src/index.ejs)

  2. 修改配置中的模板名称

    plugins: [
      new CleanWebpackPlugin(),
      new HtmlWebpackPlugin({
        template: "./src/index.ejs", // 这⼀⾏
        title: "Webpack Demo", 
      })
    ]
    
  3. 将模板文件中的图片引入,改成 ejs 语法

    <!-- 旧写法 --> 
    <img src="./images/xph.gif" alt=""> 
    <!-- 新写法 --> 
    <img src="<%= require('./images/xph.gif') %>" alt="">
    
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容