🔱webpack 从零开始搭建 vue-cli🔱

1、Webpack 🐱‍🏍

1.1 webpack 是什么?做了啥?

Webpack 是一个模块打包工具。在开发中,它把一堆文件中每个都作为一个模块处理,找出它们间的依赖关系,并打包成待发布的静态资源。

主要做了:
. 模块化
. 处理不同格式文件,整合
. 优化

列举一个基本例子,设想我们有一堆的 CommonJS 的引用。它们是不能在浏览器直接运行,所以需要把它们 捆绑<script> 标记内的单一文件。Webpack 就能按照 require() 调用的依赖关系为我们做到这点。

实际上,Webpack 能做的更多,通过 "loaders" 我们能让 Webpack 按照我们想要的任何方式打包输出。例如:

  • 编译 ES2015、CoffeeScript 或 TypeScript 模块成 ES5 CommonJS 的模块;
  • 编译之前,可以通过 linter 校验源代码。
  • 编译 Jade 模板成 HTML 并内联 JavaScript 字符串。
  • 编译 SASS 文件成 CSS,然后把生成的CSS插入到 <style> 标签内,然后再转译成 JavaScript 代码段。
  • 处理在 HTML 或 CSS 文件中引用的图片文件,根据配置路径把它们移动到任意位置,根据 MD5 hash 命名。
  • 如果你学会了Webpack,就会知道它有多么强大,它非常显著地改善你前端开发的效率。它主要的缺点是配置方式有点麻烦,但是有了我这份使用指南,那使用 Webpack + Vue + vue-loader 的时候,基本上就扫清了大多数障碍了。

1.2 webpack 的前世今生

1.2.1 前端为什么需要模块化
痛点
  • 变量和方法不容易维护,容易污染全局作用域
  • 加载资源的方式通过script标签从上到下
  • 依赖的环境主观逻辑偏重,代码较多就会比较复杂
  • 大型项目资源难以维护,特别是多人合作的情况下,资源的引入会让人奔溃
1.2.2 模块化作用

模块化的开发方式可以提供代码复用率,方便进行代码的管理。通常来说,一个文件就是一个模块,有自己的作用域,只向外暴露特定的变量和函数。 有了模块,我们就可以更方便地使用别人的代码,想要什么功能,就加载什么模块。

1.2.3 模块规范

但是,这样做有一个前提,那就是大家必须以同样的方式编写模块,否则你有你的写法,我有我的写法,岂不是乱了套!考虑到Javascript模块现在还没有官方规范,这一点就更重要了。 目前流行的js模块化规范有CommonJS、AMD、CMD以及ES6的模块系统。

  • CommonJS

NodeJS诞生之后,它使用CommonJS的模块化规范。从此,js模块化开始快速发展。 它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global

  • AMD

CommonJS对服务器端不是一个问题,因为所有的模块都存放在本地硬盘,可以同步加载完成,等待时间就是硬盘的读取时间。但是,对于浏览器,这却是一个大问题,因为模块都放在服务器端,等待时间取决于网速的快慢,可能要等很长时间,浏览器处于"假死"状态。 因此,浏览器端的模块,不能采用"同步加载"(synchronous),只能采用"异步加载"(asynchronous)。这就是AMD规范诞生的背景。

AMD是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义"。它采用异步方式加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。 主要有两个Javascript库实现了AMD规范:require.js和curl.js

  • CMD

CMD是另一种js模块化方案,它与AMD很类似,不同点在于:AMD推崇依赖前置、提前执行,CMD推崇依赖就近、延迟执行。 此规范其实是在sea.js推广过程中产生的。

  • ES6

在语言标准的层面上,实现了模块功能,而且实现得相当简单,旨在成为浏览器和服务器通用的模块解决方案。其模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。

1.3 为什么需要webpack呢?🧡

前端页面效果越来越酷炫、功能越来越复杂。而前端工程师们为了更方便的开发提高开发效率进行了一系列der探索,模块化思想的提出啊,将复杂的程序分割成更小的文件。(模块化)这些年优秀的框架层出不穷react、vue、angular、es6这种在javascript基础上拓展的新的语法规范和 less、sass、css处理器等等等。 所有的事物都是具有双面性的、有利有弊。大大提高开发效率的同时,又为后期维护造成了困扰。因为利用这些工具的文件往往不能直接被浏览器识别,需要手动处理,很影响开发进度(处理整合)

是否可以有一种方式,不仅可以让我们编写模块,而且还支持任何模块格式(至少在我们到达 ESM 之前),并且可以同时处理资源和资产? 所以webpack应运而生~这就是 webpack 存在的原因。它是一个工具,可以打包你的 JavaScript 应用程序(支持 ESM 和 CommonJS),可以扩展为支持许多不同的静态资源,例如:images, fonts 和 stylesheets。

webpack 关心性能和加载时间;它始终在改进或添加新功能,例如:异步地加载 chunk 和预取,以便为你的项目和用户提供最佳体验。

更多深入原理文章 ❗❗❗

1.4 webpack相关的安装

pnpm add -D webpack webpack-cli webpack-dev-serve 

1.3 webpack-demo 文件目录

|- package.json
|- package-lock.json
|- /dist
|- /public
|-index.html
|- /src
|- index.js

2、node -- path 小知识 👨‍👨‍👧‍👦

2.1 path模块是什么呢?

Node.js path 模块提供了一些用于处理文件路径的小工具,我们可以通过以下方式引入该模块:

const { normalization, resolve, join, extname } = require('path')

2.2 代码如下所示

var path = require("path");

// 格式化路径
console.log('normalization : ' + path.normalize('/test/test1//2slashes/1slash/tab/..'));

// 连接路径(相当于字符串拼接)
console.log('joint path : ' + path.join('/test', 'test1', '2slashes/1slash', 'tab', '..'));

// 转换为绝对路径
console.log('resolve : ' + path.resolve('main.js'));

// 路径中文件的后缀名
console.log('ext name : ' + path.extname('main.js'));

2.3代码执行结果如下

$ node main.js 
normalization : /test/test1/2slashes/1slash
joint path : /test/test1/2slashes/1slash
resolve : /web/com/1427176256_27423/main.js
ext name : .js

3、基础配置 👻

3.1创建 webpack.config.js 文件

const { resolve } = require('path')
module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'dist.js', // [name].[contenthash].js
    path: resolve(__dirname, 'dist'),
  },
  devServer: { static: './' },
  devtool: 'source-map',
}

3.2 配置package.json -- script

serve: 启动服务

build: 打包

{
    // 这里做了一个环境的区分
    "serve": "webpack-dev-server --mode=development --hot",
	"build": "webpack --mode=production",
}

4、webpack-dev-server -- node服务 🤖

4.1 配置

devServer: { static: './' },  // 端口指向当前项目的根路径
// open -- 启动时候打开窗口
// hot -- 热更新
// port -- 端口号
// 更多配置看文档

4.2 运行命令

npx webpack-dev-server

5、plugin插件 🐱‍👤

5.1 插件作用?

loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量。

webpack.config.js

module.exports = {
  module: {
    rules: [{ test: /\.txt$/, use: 'raw-loader' }],
  },
  plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })],
};

6、 loader 🐱‍💻

6.1什么是loader?

webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。

Warning

webpack 的其中一个强大的特性就是能通过 import 导入任何类型的模块(例如 .css 文件),其他打包程序或任务执行器的可能并不支持。我们认为这种语言扩展是很有必要的,因为这可以使开发人员创建出更准确的依赖关系图。

在 webpack 的配置中,loader 有两个属性

  1. test 属性,识别出哪些文件会被转换。
  2. use 属性,定义出在进行转换时,应该使用哪个 loader

webpack.config.js

const path = require('path');

module.exports = {
  output: {
    filename: 'my-first-webpack.bundle.js',
  },
  module: {
    rules: [{ test: /\.txt$/, use: 'raw-loader' }],
    // “嘿,webpack 编译器,当你碰到「在 require()/import 语句中被解析为 '.txt' 的路径」时,在你对它打包之前,先 use(使用) raw-loader 转换一下。
  },
};

7、模式(mode)👹

通过选择 development, productionnone 之中的一个,来设置 mode 参数,你可以启用 webpack 内置在相应环境下的优化。其默认值为 production

webpack.config.js

module.exports = {
  mode: 'production',
};

CLI 参数中传递

webpack --mode=development

支持以下字符串值:

选项 描述
development 会将 DefinePluginprocess.env.NODE_ENV 的值设置为 development. 为模块和 chunk 启用有效的名。
production 会将 DefinePluginprocess.env.NODE_ENV 的值设置为 production。为模块和 chunk 启用确定性的混淆名称,FlagDependencyUsagePluginFlagIncludedChunksPluginModuleConcatenationPluginNoEmitOnErrorsPluginTerserPlugin
none 不使用任何默认优化选项

如果没有设置,webpack 会给 mode 的默认值设置为 production

8、常用插件 😋

8.1 html-webpack-plugin

  • html-webpack-plugin插件的基本作用就是生成html文件

  • webpack-dev-server 则会打开这个html文件,

  • webpack 命令也是打包这个html文件,并将打包后的js引入

  • 为html文件中引入的外部资源如script、link动态添加每次compile后的hash,防止引用缓存的外部文件问题

    webpack.config.js

plugins: [
    new HtmlWebpackPlugin({
    	// resolve -- 绝对路径, 
    	// __dirname -- 当前文件夹名字, 
    	// 'public/index.html' -- 拼接的路径
        template: resolve(__dirname, 'public/index.html'),
    }),
],

8.2 VueLoaderPlugin

 const { VueLoaderPlugin } = require('vue-loader') // 在 vue-loader 中引入
 
 plugins: [
    new VueLoaderPlugin(),
 ]

这个插件是必须的! 它的职责是将你定义过的其它规则复制并应用到 .vue 文件里相应语言的块。例如,如果你有一条匹配 /\.js$/ 的规则,那么它会应用到 .vue 文件里的 <script> 块。

8.2 eslint-webpack-plugin

可以在每次编译时进行 eslint 的语法校验

webpack.congfig.js

new ESLintWebpackPlugin({
	fix: true,
}),

9、常用 loader🐼

9.1 vue-loader

具体配置推荐看:vue-loader 文档地址 💻

Vue Loader 是一个 webpack 的 loader,它允许你以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件, 也就是解析以 *.vue 结尾的文件

<template>
  <div class="example">{{ msg }}</div>
</template>

<script>
export default {
  data () {
    return {
      msg: 'Hello world!'
    }
  }
}
</script>

<style>
.example {
  color: red;
}
</style>

用途:

  • vue-loader 用于识别.vue文件
  • vue 不用多说,识别支持vue语法
  • vue-template-compiler 语法模板渲染引擎 {{}} templatescriptstyle

安装

// vue-template-compiler 辅助 vue-loader 编译成对应 vue 版本的代码
npm install -D vue-loader vue-template-compiler

这里需要注意的是:

  • vue2不能工作在vue-loader17以上,需要降到
npm i -D vue-loader@15.9.8
  • vue-template-compiler 版本需要和 vue 版本对应

9.2 babel-loader

Babel 是一个工具链,主要用于将采用 ECMAScript 2015+ 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。

下面列出的是 Babel 能为你做的事情:

  • 语法转换
  • 通过 Polyfill 方式在目标环境中添加缺失的特性 (通过引入第三方 polyfill 模块,例如 core-js

@babel/core:babel转译器本身,提供了babel的转译API,如babel.transform等,用于对代码进行转译。webpack的babel-loader就是调用这些API来完成转译过程的。

// 它会应用到普通的 `.js` 文件
// 以及 `.vue` 文件中的 `<script>` 块
{
    test: /\.(js|jsx|ts\tsx)$/,
    loader: 'babel-loader',
    exclude: 'node_modules/',
},

9.3 vue-style-loader && css-loader

用途:用于处理文件中的 css style

// 它会应用到普通的 `.css` 文件
// 以及 `.vue` 文件中的 `<style>` 块
{
    test: /\.css$/,
    use: ['vue-style-loader', 'css-loader'],
},

9.4 url-loader

用途:url-loader也是处理图片类型资源,只不过它与file-loader有一点不同,url-loader可以设置一个根据图片大小进行不同的操作,如果该图片大小大于指定的大小,则将图片进行打包资源,否则将图片转换为base64字符串合并到js文件里

 {
     test: /\.(png|jpg|jpeg)$/,
     use: [
         {
             loader: "url-loader",
             options: {
                 name: "[name]_[hash:8].[ext]",
                 limit: 10240, // 这里单位为(b) 10240 => 10kb
                 // 这里如果小于10kb则转换为base64打包进js文件,如果大于10kb则打包到dist目录
         	}
         }
     ]
 }

9.5 file-loader

用途: 用于处理文件类型资源,如jpgpng等图片。返回值为publicPath为准。

{
        test: /\.(png|jpeg|gif|webp)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              // 如果打包出现img的src路径为[Object Module],解决方案有
              // 将file-loader降级到4.2.0
              // 修改options参数esModule为false
              esModule: false,  // file-loader 默认使用 ES6 模块解析,将其关闭,启用 CommonJS 模块,不配置这个,html 文件中的图片路径不对
              name: "img/[name]_[hash:6].[ext]",
              publicPath: "http://127.0.0.1:8080"  //前缀
            },
          }
        ],
},

9.6 less-loader

用途:处理 less 文件。

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

9.7 postcss-loader

用途与插件:

  • autoprefixer (添加浏览器前缀)
  • precss(可以像写预处理器一样写css)
  • cssnext (可以使用最新的css语法)
  • cssnano(压缩css)
  • stylelint (CSS 检测器,支持新css语法校验)
  • postcss-modules
  • 过使用 PostCSS 来实现 scpoed

PostCSS 的配置可以通过 postcss.config.jspostcss-loader 选项来完成。

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

10、 loader 具体配置😈

10.1 起步

vue-loader、const { VueLoaderPlugin } = require('vue-loader')

10.2 处理资源路径

file-loader

10.3 使用预处理器

less-loader

10.4 Scoped CSS

postcss-loader

10.5 CSS Modules

CSS Modules 是一个流行的,用于模块化和组合 CSS 的系统。vue-loader 提供了与 CSS Modules 的一流集成,可以作为模拟 scoped CSS 的替代方案。

// webpack.config.js
{
  module: {
    rules: [
      // ... 其它规则省略
      {
        test: /\.css$/,
        use: [
          'vue-style-loader',
          {
            loader: 'css-loader',
            options: {
              // 开启 CSS Modules
              modules: true,
              // 自定义生成的类名
              localIdentName: '[local]_[hash:base64:8]'
            }
          }
        ]
      }
    ]
  }
}

对着上面官网的配置会报错 - options has an unknown property 'localIdentName'.

然后我的配置改成这样,便成功编译

{
    test: /\.css$/,
    use: [
    	'vue-style-loader',
        {
            loader: 'css-loader',
            options: {
                modules: {
                    localIdentName: '[name]__[local]--[hash:base64:5]'
                }
            }
        },
        'postcss-loader'
    ],
},

10.6 热重载

“热重载”不只是当你修改文件的时候简单重新加载页面。启用热重载后,当你修改 .vue 文件时,该组件的所有实例将在不刷新页面的情况下被替换。它甚至保持了应用程序和被替换组件的当前状态!当你调整模版或者修改样式时,这极大地提高了开发体验。

webpack-dev-server --hot

11、 最终代码👺

package.json

{
  "name": "myvue",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "serve": "webpack-dev-server --mode=development --hot",
    "build": "webpack --mode=production",
    "lint": "eslint  ./src --ext .js,.vue --cache",
    "lint:fix": "eslint  ./src --ext .js,.vue --fix --cache",
    "format": "prettier --write \"./src/*.{html,vue,ts,js,json,md}\""
  },
  "keywords": [
    "vue",
    "vue-router",
    "keep-alive",
    "eslint",
    "vue-cli"
  ],
  "author": "jHong",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.12.0",
    "babel-loader": "^9.1.2",
    "css-loader": "*",
    "eslint": "^8.36.0",
    "eslint-webpack-plugin": "^4.0.0",
    "html-webpack-plugin": "^5.5.0",
    "prettier": "^2.8.7",
    "vue-loader": "15.9.8",
    "vue-style-loader": "^4.1.3",
    "vue-template-compiler": "^2.7.14",
    "webpack": "^5.76.3",
    "webpack-cli": "^5.0.1",
    "webpack-dev-server": "^4.13.1"
  },
  "dependencies": {
    "vue": "2.6"
  }
}

webpack.config.js

const { resolve } = require('path'),
  HtmlWebpackPlugin = require('html-webpack-plugin'),
  ESLintWebpackPlugin = require('eslint-webpack-plugin'),
  { VueLoaderPlugin } = require('vue-loader')

module.exports = {
  entry: './src/index.js',
  output: {
    filename: '[name].[contenthash].js',
    path: resolve(__dirname, 'dist'),
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: resolve(__dirname, 'public/index.html'),
    }),
    new ESLintWebpackPlugin({
      fix: true,
    }),
    new VueLoaderPlugin(),
  ],
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      },
      // 它会应用到普通的 `.js` 文件
      // 以及 `.vue` 文件中的 `<script>` 块
      {
        test: /\.(js|jsx|ts\tsx)$/,
        loader: 'babel-loader',
        // exclude: 'node_modules',
      },
      // 它会应用到普通的 `.css` 文件
      // 以及 `.vue` 文件中的 `<style>` 块
      {
        test: /\.css$/,
        use: ['vue-style-loader', 'css-loader'],
      },
      // 它会应用到普通的 `.图片` 文件
      {
        test: /\.(png|jpeg|gif|webp)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              // 如果打包出现img的src路径为[Object Module],解决方案有
              // 将file-loader降级到4.2.0
              // 修改options参数esModule为false
              esModule: false,  // file-loader 默认使用 ES6 模块解析,将其关闭,启用 CommonJS 模块,不配置这个,html 文件中的图片路径不对
              name: "img/[name]_[hash:6].[ext]",
              publicPath: "http://127.0.0.1:8080"  //前缀
            },
          }
        ],
      },
    ],
  },
  devServer: { static: './' },
  devtool: 'source-map',
  resolve: {
    alias: {
      //告诉webpack, @ 符号表示 src目录
      '@': resolve(__dirname, './src/'),
      'vue$': 'vue/dist/vue.runtime.common.js',
      // 'vue$': 'vue/dist/vue.common.js',
    },
  },
}

12、 总结💤

  1. 运行时版本没有编译器(打包后体积更小),需要配合loader, 处理成 render。
  2. vue-cli 就是 webpack 配置好各种 loader、plugins 的一个产物,它可以做到开箱即用。
posted on 2023-04-01 11:51  京鸿一瞥  阅读(177)  评论(1编辑  收藏  举报