nodejs、npm、webpack、vue-cli入门及使用

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

作为一个后端开发,每次碰到前端项目(多半是后台管理)时都十分头疼,每次都要从头开始学习Node、npm、webpack、vue-cli,这次索性就把入门用到的相关知识和使用示例都整理出来。

个人习惯,知识梳理的时候会按照是什么、能做什么、怎么用的思路进行整理。

一、Nodejs

1.简介(是什么)

Node.js 是一个开源、跨平台的 JavaScript 运行时环境(这是官网的介绍)。
Node.js 是一个基于 V8 JavaScript 引擎构建的 JavaScript 运行时(中文文档中的介绍)。相当于把chrome的V8引擎单独拎出来做了一个js的运行时环境。
Node.js的特点是事件驱动和异步I/O。
官网地址:https://www.nodejs.com.cn/

2.目的(能做什么)

nodejs出来以前,js代码只能在浏览器中运行,这就是js为什么被称为前端语言的原因。但是有了nodejs之后,nodejs提供了js的运行时环境,让js能够在js上运行,这样js不仅仅能实现前端功能了,也能实现很多后端功能,比如处理文件、处理数据库、处理网络请求等。由于事件机制和异步IO模型的优越性,nodejs甚至可以实现一个高性能的web服务器。nodejs还支持模块化开发,让js代码的维护、扩展、复用性更好。
总的来说,通过nodejs提供的js运行时环境和模块化功能,我们可以用js实现更多复杂的功能,也能更加方便的使用js。

3.使用(怎么用)

3.1安装

参考安装:https://blog.csdn.net/muzidigbig/article/details/80493880

3.2使用脚本模式

脚本模式就是使用node.js运行一个js脚本
首先,新建一个js文件hello.js,内容如下

console.log("Hello World");

然后通过 node命令来执行:

node helloworld.js

程序执行后,正常的话,就会在终端输出 Hello World。

3.3使用交互模式

打开终端,键入node进入命令交互模式,可以输入一条js代码语句后立即执行并显示结果,例如:

$ node
> console.log('Hello World!');
Hello World!

3.4使用 Node.js 编写网络服务器

a.首先,创建一个名为 projects 的空项目文件夹
b.然后,在 projects 文件夹中创js文件并将其命名为 hello-world.js。
代码如下:

const http = require('node:http');//引入node的http模块

const hostname = '127.0.0.1';
const port = 3000;
//创建一个server,响应固定为“Hello,World”
const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello, World!\n');
});
//监听端口
server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

c.最后,执行node命令运行js文件。

node hello-world.js

控制台会输出以下结果

Server running at http://127.0.0.1:3000/

访问http://127.0.0.1:3000,会出现Hello, World!,表示服务器运行成功。

3.5Node.js模块化机制

Node.js采用了模块化的开发方式,使开发者能够将功能拆分为独立的模块,并通过模块间的导入和导出实现代码的复用和组织。在Node.js中,每个文件都被视为一个模块,模块可以是一个函数、对象或类等。
Node.js的模块化遵循的是CommonJS规范,使用require函数来导入其他模块,并使用module.exports或exports来导出当前模块的内容。通过这种方式,可以将变量、函数、类等封装在模块中,并在需要的地方进行导入和使用。

模块化的好处
1.代码重用:通过将功能划分为模块,可以将已经开发和测试过的模块在不同的项目中重复使用,提高代码的复用性和开发效率。
2.可维护性:模块化设计使得软件的各个部分相对独立,当需要修改或修复某个功能时,只需关注特定的模块,而无需对整个系统进行大规模的改动,简化了维护工作。
3.并行开发:模块化可以让不同的开发人员并行工作,在保证模块接口一致性的前提下,开发团队可以独立地开发、测试和调试各自负责的模块,提高开发效率。
4.可测试性:模块化设计使得单个模块的功能较小且相对独立,这使得单元测试和集成测试更容易进行,能够更准确地定位和解决问题。
5.可扩展性:通过模块化设计,可以更方便地添加新功能或替换现有功能,只需修改或增加相关模块,而不必影响整个系统的其余部分。
require加载路径的逻辑
1.对于自己创建的模块,导入时路径建议写 相对路径 ,且不能省略 ./ 和 …/
2.js 和 json 文件导入时可以不用写后缀,c/c++编写的 node 扩展文件也可以不写后缀,但是一
般用不到
3.如果导入其他类型的文件,会以 js 文件进行处理
4.如果导入的路径是个文件夹,则会 首先 检测该文件夹下 package.json 文件中 main 属性对应
的文件,如果存在则导入,反之如果文件不存在会报错。
5.如果 main 属性不存在,或者 package.json 不存在,则会尝试导入文件夹下的 index.js 和
index.json ,如果还是没找到,就会报错
6.导入 node.js 内置模块时,直接 require 模块的名字即可,无需加 ./ 和 …/
require 导入的基本流程
1.将相对路径转为绝对路径,定位目标文件
2.缓存检测
3.读取目标文件代码
4.包裹为一个函数并执行(自执行函数)。通过 arguments.callee.toString() 查看自执行函数
5.缓存模块的值
6.返回 module.exports 的值

只需要记住require返回的是module.exports 的值就可以了

参考文章
1.nodejs中文文档:https://www.nodejs.com.cn/api/synopsis.html
2.【NodeJS】——模块化:https://blog.csdn.net/weixin_50233139/article/details/131471635


二、npm

1.简介

npm 是 Node.js 官方提供的包管理工具,他已经成了 Node.js 包的标准发布平台,用于 Node.js 包的发布、传播、依赖控制。npm 提供了命令行工具,使你可以方便地下载、安装、升级、删除包,也可以让你作为开发者发布并维护包。

2.为什么用npm

在以前,前端是怎么共享代码的呢?在 GitHub 还没有兴起的年代,前端是通过网址来共享代码。比如你想使用 jQuery ,那么你点击 jQuery 网站上提供的链接就可以下 jQuery ,放到自己的网站上使用 。GItHub 兴起之后,社区中也有人使用 GitHub 的下载功能。
但是当一个项目依赖的代码越来越多,并且一个依赖还会依赖另一个依赖,从多个网址来下载再依赖到自己的项目中是很麻烦的事情,同时版本的管理也会非常麻烦。所以npm提供了一种方案来解决包依赖的问题。
1.包的作者使用 npm publish 把代码提交到 repository 上,并给包命名;
2.使用者将包名写到 package.json 里,然后运行 npm install ,npm 就会帮他们下载代码(包所依赖的其他包也会自动下载);
3.下载完的代码出现在 node_modules 目录里,就可以随意使用了。

3.npm的使用

3.1安装npm

由于node.js内置了npm,所以安装好node后就不用安装npm了。可以通过输入 “npm -v” 来测试是否成功安装。
如果版本过低,可以通过如下命令升级:

npm install npm -g

由于默认registry(类似maven的中央仓库)在国外,下载速度慢,可以使用淘宝镜像,设置如下:
如果想要更改默认的registry地址为国内的镜像源,比如使用淘宝NPM镜像,则需要运行以下命令:

npm config set registry https://registry.npmmirror.com

这会将默认的registry地址修改为https://registry.npmmirror.com。
若要确保新的配置生效,再次运行 npm config get registry 命令来获取最新的registry地址。

3.2安装包

通过package.json安装

package.json 文件其实就是对项目或者模块包的描述,里面包含许多元信息。比如项目名称,项目版本,项目执行入口文件,项目贡献者等等。
这是官方推荐的安装包的方式,使用这种方式的好处是:
1.package.json文件可以把所有的依赖包以列表的形式展示出来,一目了然
2.可以使用semantic versioning rules这种语法来指定依赖包的版本
3.能让你的项目构建具有可复制性,更容易分享个其他开发者

认识package.json

{
  "main": "index.js",#默认的入口文件
  "dependencies": {},#生产环境依赖
  "devDependencies": {},#本地开发和测试环境依赖
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
    "build": "webpack"
  },
}

main字段:当你在代码中使用 require() 函数导入一个模块时,Node.js 会根据 package.json 文件中的 main 字段来确定该模块的入口文件。例如,如果你引入了一个名为 myModule 的模块,并且myModule模块的package.json文件中的 main 字段设置为 index.js,那么当其他模块使用 require(‘myModule’) 导入 myModule 时,实际上会执行 index.js 文件中的代码。

dependencies和和devDependencies字段:dependencies是生产环境依赖,build打包时会打到生产包里去,devDependencies是本地开发和测试环境依赖,本地运行时会依赖到,但是build打包时不会打到生产包里

scripts字段:用于执行npm script,后续讲webpack的例子时会明白了

具体的安装方式如下:
1.执行npm init命令

npm init

这时会出现一些问答,比如给你的项目命名、项目描述、作者信息等等,如果不想设置,一路回车就可以了,完事后会在你执行命令的目录下生成一个package.json文件
也可以通过npm init --yes或者npm init -y命令跳过问答,这样会生成一个默认的package.json文件,如下

{
  "name": "npm-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",#默认的入口文件
  "dependencies": {},#生产环境依赖
  "devDependencies": {},#本地开发和测试环境依赖
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

2.在dependencies对象中加入你想要依赖的包和版本

前略
"dependencies": {
    "express": "^4.19.1",
    "webpack": "^5.91.0"
  },
后略

3.执行npm install命令

npm install

这样npm就会自动帮你下载你要依赖的包,并放在node_modules目录里
在这里插入图片描述

命令行安装

1.本地安装
一般会看到3种安装命令,如下

npm install <Module Name>
npm install <Module Name> --save
npm install <Module Name> --save-dev //仅开发环境依赖,生产环境不依赖

npm install --save命令就是指安装包并且把包名和版本保存到package.json文件的dependencies对象中,npm install --save-dev命令就是指安装包并且保存到package.json文件的devDependencies对象中。显然,这两个命令需要package.json文件,如果没有,执行后就会提示找不到文件,如下(但是包还是安装成功了,只是没有把包名和版本保存到package.json文件的dependencies或devDependencies对象中)在这里插入图片描述
最后,npm install 命令就是安装包到node_modules目录里。另外也可以通过npm install @版本号 来指定安装的版本。

2.全局安装
如果你想将其作为一个命令行工具,那么你应该将其安装到全局。这种安装方式后可以让你在任何目录下使用这个包。比如 grunt 就应该以这种方式安装。

npm install -g <Module Name> 

这种安装方式会安装到全局目录%USERPROFILE%\AppData\Roaming\npm\node_modules下
3.更新和卸载
本地更新:
在 package.json 文件所在的目录中执行 npm update 命令
本地删除:
如需删除 node_modules 目录下面的包(package),请执行:
npm uninstall 命令,如果需要从 package.json 文件中删除依赖,需要在命令后添加参数 --save:npm uninstall --save

3.3发布包

1.首先,要注册npm用户
登录官网https://www.npmjs.com/根据提示注册
2.使用命令行登录(因为需要用命令行发布)

npm login

根据提示输入用户名、密码、邮箱验证码就登录成功了
(这里要注意的是,前面使用官网注册的话,需要使用默认的registry地址。如果前面已经改为https://registry.npmmirror.com,这里需要再改回https://registry.npmjs.org/。)
3.准备需要发布的项目
执行npm init ,初始化项目名、版本、作者,生成package.json,再创建index.js和README.md文件,如下:
在这里插入图片描述

{
  "name": "ademobyzcl",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "dependencies": {},
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "zhchling86",
  "license": "ISC"
}

index.js内容如下(按中文手册):

exports.printMsg = function() {
  console.log("This is a message from the demo package");
}

README.md随便描述一下即可
4.发布
执行npm publish,即可发布成功了
5.使用自己发布的包
新建一个项目,依赖刚刚发布的包ademobyzcl

{
  "name": "npm-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "ademobyzcl": "^1.0.0"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

执行npm install,npm-demo项目的node_modules目录下就下载好ademobyzcl包啦
下载好后怎么使用呢?按中文手册中所述,使用require()来引入module,那什么是module呢?

A folder with a package.json file containing a main field.//一个含有package.json的文件夹,且package.json中含有main字段,也就是入口
A folder with an index.js file in it.//一个含有index.js的文件夹(与第一种类似,因为index.js就是默认的入口)
A JavaScript file.//一个js文件

显然,我们的ademobyzcl包属于第一种
那么我们在项目中新建一个js文件main.js,引入ademobyzcl包,如下:

const ademo = require('ademobyzcl')

ademo.printMsg();

在main.js所在文件目录下执行一下命令,就可以运行了

node main.js

执行结果如下:
在这里插入图片描述
ademobyzcl包中的log就打印出来啦

另外,也可以使用npm命令来执行main.js,需要在package.json中添加script,如下

“scripts”: {
“dev”:“node main.js”,
“test”: “echo “Error: no test specified” && exit 1”
},

再执行npm run dev,同样ademobyzcl包中的log也打印出来啦
在这里插入图片描述

参考文档:npm中文手册https://www.npmjs.cn/getting-started/using-a-package.json/

三、webpack

1.简介

前面讲nodejs和npm都是针对的js,还没涉及到html、css和浏览器,而webpack就是用来给项目打包的一个工具。按官网文档介绍,本质上,webpack 是一个用于现代 JavaScript 应用程序的静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。

2.目的

在单体web项目中,我们把静态代码和java代码一起打包成war后部署在tomcat上。而前后端分离后,前端工程需要单独进行打包、部署,此时便需要打包工具了。另外我们想使用更加高级的语法时(比如less和sass),想使用模块化,而浏览器不支持怎么办,又需要进行转化,这也离不开打包工具。因此,webpack打包工具有如下作用:
1.打包:将所有模块打包构建成静态资源,相比于通过script标签,script标签引入是下载全部的js,而webpack打包只会将用到的js打包进来。
2.代码压缩:将JS、CSS代码混淆压缩,让代码体积更小,加载更快
3.编译语法:编写CSS时使用Less、Sass,编写JS时使用ES6、TypeScript等,这些标准目前都无法被浏览器兼容,因此需要构建工具编译,例如使用Babel编译ES6语法。
4.处理模块化:CSS和JS的模块化语法,目前都无法被浏览器兼容。因此开发环境可以使用既定的模块化语法,但是需要构建工具将模块化语法编译为浏览器可识别形式。
(ps:其他的构建工具:最早普及使用的是Grunt,后面又出现Gulp。Webpack是目前流行的构建工具)

webpack模块化与nodejs模块化的区别(个人理解)

前面介绍过,nodejs模块化是基于common.js规范的,使用require函数来导入其他模块,并使用module.exports或exports来导出当前模块的内容。
webpack是在nodejs环境下的打包工具,显然nodejs模块化方式webpack也能用,而webpack还支持ES6的模块化方式,及通过export和import来导出和导入js

3.安装

本地安装

首先创建进入一个目录并初始化 npm,然后在本地安装 webpack和webpack-cli,-D就是–save-dev的简写,即保存到package.json的devDepencies对象中

mkdir webpack-demo
cd webpack-demo
npm init -y
npm install webpack webpack-cli -D

在这里插入图片描述
安装完后执行.\node_modules.bin\webpack -v(因为是本地安装,需要加上本地安装的路径),查看是否安装成功(Node 8.2/npm 5.2.0 及以上版本提供的 npx 命令,可以运行在最初安装的 webpack 包中的 webpack 二进制文件npx webpack -v)
在这里插入图片描述

4.webpack使用(无配置)

不使用webpack,通过 script 脚本引入

现在创建以下目录结构、文件和内容:

webpack-demo
  |- package.json //npm init -y后会生成
  |- package-lock.json //安装完webpack后会生成
 |- index.html
 |- /src
   |- index.js

src/index.js内容:

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

  // 执行这一行需要引入 lodash(目前通过 script 脚本引入,假定_作为全局变量)
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');

  return element;
}

document.body.appendChild(component());

index.html内容:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>起步</title>
    <script src="https://unpkg.com/lodash@4.17.20"></script>
  </head>
  <body>
    <script src="./src/index.js"></script>
  </body>
</html>

用浏览器打开index.html,显示Hello webpack
在这里插入图片描述
在此示例中,

通过 webpack构建

首先,本地安装ladash

npm install --save lodash

然后显示的引入lodash

import _ from 'lodash';

function component() {
  const element = document.createElement('div');
  element.innerHTML = _.join(['Hello', 'webpack'], ' ');

  return element;
}

document.body.appendChild(component());

执行webpack命令,此时会默认从./src/index.js开始进行打包,生成dist目录,dist目录下有main.js,就是打包后的js

npx webpack

在这里插入图片描述
最后修改index.html重新引入main.js

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>起步</title>
  </head>
  <body>
    <script src="./dist/main.js"></script>
  </body>
</html>

用浏览器打开index.html,也会显示Hello webpack

另外,可以通过npm scripts来简化构建的执行,在package.json的scripts对象中添加"build": “webpack”,使用 npm scripts 便可以像使用 npx 那样通过模块名引用本地安装的 npm 包

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

这样,直接运行npm run build就可以完成构建了

5.webpack使用(有配置)

上面介绍了webpack可以不通过配置运行,然而大多数项目会需要很复杂的设置,因此 webpack 仍然支持配置文件,这比在终端中手动输入大量命令更加高效。

基本配置

entry:表示 webpack 的入口,指定打包的文件,可以一个或多个。

output:表示输出文件的路径和文件名。

mode:表示打包环境,可设置 production 或 development 值。
在项目目录下创建文件webpack.config.js:

const path = require('path');

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

上面配置会解析 src 下的 app.js 文件的相关依赖并打包,然后输出到当前项目的 dist 文件夹下,输出文件名为 main.js。

output 中的 clean 会在打包生成文件之前会按照这个字段的值进行清理行为,如果为 true,则清空 output 设定的目录下的文件。

模式为开发模式,开发模式可以编译 ES Module语法,而生成模式(production)则不仅可以编译 ES Module语法,还可以压缩代码。

loader

webpack 的 Loader 主要用于对模块的转换,也就是对文件的转化,如处理CSS,将 Less、Sass 处理成 CSS,将图片处理成 data URL 等。

下面以处理CSS为例介绍,其他处理如图片、字体等详见官网文档
1.安装 style-loader 和 css-loader,并在 module 配置 中添加这些 loader:

npm i css-loader style-loader -D

2.新建webpack.config.js文件:

const path = require('path');

 module.exports = {
   entry: './src/index.js',
   output: {
     filename: 'main.js',
     path: path.resolve(__dirname, 'dist'),
   },
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
 };

注意:module loader 可以链式调用。链中的每个 loader 都将对资源进行转换,不过链会逆序执行。第一个 loader 将其结果(被转换后的资源)传递给下一个 loader,依此类推。最后,webpack 期望链中的最后的 loader 返回 JavaScript。
请确保 loader 的先后顺序:‘style-loader’ 在前,而 ‘css-loader’ 在后。如果不遵守此约定,webpack 可能会抛出错误。
3.在src目录下新建 style.css 文件

.hello {
  color: red;
}

4.修改src/index.js:

import _ from 'lodash';
import './style.css';

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

   // lodash 现在使用 import 引入。
   element.innerHTML = _.join(['Hello', 'webpack'], ' ');
   element.classList.add('hello');

   return element;
 }

 document.body.appendChild(component());

在package.json的scripts字段中增加build命令

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --config webpack.config.js"
  },

运行npm run build命令,就会将js和css打包成dist/main.js了(如果–config webpack.config.js不加默认就是找webpack.config.js配置)
用浏览器打开index.html,也会显示红色的Hello webpack啦

plugin

webpack 的插件能够增强 webapck 的功能,比如提取 CSS 为单独文件、自动引入 JS 和 CSS、压缩 CSS。

html-webpack-plugin
最常用的是自动引入 JS 和 CSS
打包后,不管是 JS 还是 CSS 打包为单独文件时,在 index.html 中都需要手动引入相应的资源,如果修改了 webpack 打包 JS 和 CSS 文件的路径,那么 index.html 也需要修改。
通过 HtmlWebpackPlugin 插件,自动生成index.html 并引入JS和CSS,便能解决上述问题

首先安装插件:

npm i html-webpack-plugin -D

然后修改 webpack.config.js 配置:

...
const HtmlWebpackPlugin = require('html-webpack-plugin');
...
plugins: [
    new HtmlWebpackPlugin({
      title: '管理输出',
      template: path.resolve(_dirname,'index.html')
    }),
  ],

template的作用是将 index.html作为模板再引入JS和CSS,否则生成的index.html就只有js和css,没有其他内容
打包后,可看到在 dist 下有一个 index.html

copy-webpack-plugin
有一些静态资源文件。不通过webpack打包。而是手动复制到打包文件里面。这时候,就需要利用这个plugin来帮助我们自动复制。
这个功能也很常用,后面介绍vue-cli时就有使用

首先安装:

npm install --save-dev copy-webpack-plugin

常用 API:

1.patterns:一个数组,包含要复制的源文件和目标文件的信息。每个数组元素都是一个对象,其中包含以下属性:
from:源文件路径或模式。可以是字符串或正则表达式。
to:目标文件路径。可以是字符串或函数。默认是compiler.options.output,也就是打包输出的路径
toType:目标文件类型。可以是 'file''dir'。默认值是 'file'。
flatten:是否将源文件复制到目标文件的子目录中。默认值是 false。
transform:一个函数,用于在复制文件之前对源文件进行转换。
noErrorOnMissing:为ture表示找不到目标文件时不会报错
2.options:一个对象,包含一些全局选项,如 concurrency(并发复制的文件数)和 overwrite(是否覆盖已存在的文件)。
const CopyPlugin = require('copy-webpack-plugin');

module.exports = {
  plugins: [
    new CopyPlugin({
      patterns: [{
          from: 'src/index.html',
          to: 'dist/index.html'
        },
        {
          from: 'src/assets',
          to: 'dist/assets'
        },
        {
          from: 'src/images',
          to: 'dist/images',
          toType: 'dir'
        },
        {
          from: 'src/styles.css',
          to: 'dist/styles.css',
          transform: (content, path) => minifyCSS(content)
        },
        {
          from: '**/*',
          to: './',
          globOptions: {
            ignore: ['**/*.js', '**/*.scss', '**/*.ts']
          }
        },
        {
          from: "public/**/*",
          filter: async (resourcePath) => {
            const data = await fs.promises.readFile(resourcePath);
            const content = data.toString();

            if (content === "my-custom-content") {
              return false;
            }

            return true;
          },
        },
      ],
      options: {
        concurrency: 10,
        overwrite: true
      }
    })
  ]
};

在这个示例中,我们创建了一个 CopyWebpackPlugin 实例,并指定了要复制的文件和目录。第一个模式将 src/index.html 文件复制到 dist/index.html 文件,第二个模式将 src/assets 目录复制到 dist/assets 目录,第三个模式将 src/images 目录复制到 dist/images 目录,第四个模式将 src/styles.css 文件复制到 dist/styles.css 文件,并在复制之前对内容进行转换(例如,压缩 CSS)。我们还指定了一些全局选项,如并发复制的文件数和是否覆盖已存在的文件。

参考文章:https://blog.csdn.net/weixin_43972437/article/details/132888267

使用 source map

当 webpack 打包源代码时,可能会很难追踪到错误和警告在源代码中的原始位置。例如,如果将三个源文件(a.js,b.js 和 c.js)打包到一个 bundle(bundle.js)中,而其中一个源文件包含错误,那么堆栈跟踪就会直接指向到 bundle.js,却无法准确知道错误来自于哪个源文件,所以这种提示通常无法提供太多帮助。

为了更容易地追踪错误与警告在源代码中的原始位置,JavaScript 提供了 source map 功能,可以帮助将编译后的代码映射回原始源代码。source map 会直接告诉开发者错误来源于哪一个源代码。

修改webpack.config.js配置

module.exports = {
   mode: 'development',
   entry: {
     index: './src/index.js',
     print: './src/print.js',
   },
  devtool: 'inline-source-map', #新增source-map配置
  ...
 }

devServer

devserver 可以在本地启动一个服务,使我们可以使用 http 协议去访问 index.html ,并且当修改代码时,能够监听当前项目的文件变动从而实现自动打包,而不用每次修改后都要手动打包:

安装webpack-dev-server,这里要特别注意,如果webpack和webpack-cli安装的是5.X,那么webpack-dev-server一定要安装4.X,我因为安装成5.X导致报错[webpack-cli] Error: Not supported,找了很久的原因才发现是版本的问题。

npm i webpack-dev-server@4.15.2 -D

修改webpack.config.js配置文件,告诉 dev server 应从什么位置开始查找文件:

...
module.exports = {
   mode: 'development',
   entry: {
     index: './src/index.js',
     print: './src/print.js',
   },
   output: {
     filename: 'main.js',
     path: path.resolve(__dirname, 'dist'),
	 publicPath: '/resource',
   },
   devtool: 'inline-source-map',
   #新增devserver的配置
  devServer: {
    static: './public',
    host: 'localhost',
	port: 8080,
	open: true
  },
  ...
 }

配置解读
前提知识:webpack构建时会从entry打包到output的path.filename中,而webpack-dev-server不会打包成文件,而是打包后放在内存中,部署到http://[devServer.host]:[devServer.port]上,可以通过http://[devServer.host]:[devServer.port]/[output.publicPath]/[output.filename] 进行访问。例如上面的配置,可以通过localhost:8080/resource/main.js访问打包后的js(事实上,项目根本没有/resource/main.js文件,因为是在内存中)。此时./public目录下的html引用main.js的路径为/resource/main.js

<script src="/resource/main.js"></script>

static: ‘./public’:告知 webpack-dev-server 将./public目录(static不配置默认也是./public)下的文件作为可访问的静态资源部署在 localhost:8080,localhost:8080默认是访问index.html。./public目录下所有文件都可以访问。
host: ‘localhost’:ip为本机
port: 8080:端口为8080
open: true:部署后在浏览器打开

运行以下命令,即可部署并打开浏览器页面了(–config是指定配置文件,默认就是webpack.config.js)

npx webpack serve --config webpack.config.js

参考文档:
webpack中文文档:https://www.webpackjs.com/guides/getting-started
webpack配置文档:https://www.webpackjs.com/configuration/dev-server/
Webpack入门,这一篇就够了:https://zhuanlan.zhihu.com/p/614835591

四、vue-cli

1.简介

在开发中,需要打包的东西不止是js、css、html。还有更多的东西要处理,这些插件和加载器如果我们一一去添加就会比较麻烦。幸好,vue官方提供了一个快速搭建vue项目的脚手架:vue-cli。使用它能快速的构建一个web工程模板。

2.安装

npm install -g vue-cli

3.使用

1)新建一个文件夹vuecli-demo
2)执行下面命令,快速搭建一个webpack的项目:

vue init webpack

一路回车,vue-rooter选择yes,ESlint和test选择no
在这里插入图片描述
创建过程参数说明:
Project name :项目名称----不能有大写字母
Project description:项目描述
Author:作者
Vue build:如何构建项目
Install vue-router:是否安装路由
Use ESLint to lint your code:是否使用ESLint来规范我们的代码
Pick an ESLint preset:选择一个ESLint代码规范
Set up unit tests:是否需要自动化单元测试
Setup e2e tests with Nightwatch:是否需要自动化用户界面测试
Should we run ‘npm install’ for your after the project has been created?(recommend):在后续安装依赖包是是否使用npm install安装

3)执行npm run dev,即可启动项目,访问localhost:8080,即可出现下面页面
在这里插入图片描述
执行npm run build命令,构建项目,打包到dist目录

在这里插入图片描述
在这里插入图片描述

4)项目结构说明
在这里插入图片描述
build:项目webpack配置文件
config:针对开发环境和线上环境的配置文件
node_modules:项目依赖包
src:源代码目录
static:静态资源
.babelrc:JavaScript 语法的编译器
.editorconfig:针对babel的编译,浏览器配置
.eslintignore:针对babel的编译,eslint检测规则配置
.eslintrc.js:针对babel的编译,eslint检测规则配置
.gitignore:git 配置文件
.postcssrc.js:转换成css格式的插件
index.html:整个项目的入口index页,包含根实例的挂载点
package.json:定义了这个项目所需要的各种模块,以及项目的配置信息(比如名称、版本、许可证等元数据)
package-lock.json:其实package-lock就是锁定安装时的包版本号,需要上传到git上,以保证其他人在install时候,大家的依赖版本相同

5)主要配置文件说明
package.json
在这里插入图片描述
npm run dev命令就是执行webpack-dev-server --inline --progress --config build/webpack.dev.conf.js。
–line:默认是true, 如果开启inline, DevServer会在构建完变化后的代码时通过代理客户端控制网页刷新,也就是自动刷新网页
–progress:显示部署的百分比进度

webpack.dev.conf.js

const devWebpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
  },
  // cheap-module-eval-source-map is faster for development
  devtool: config.dev.devtool,

  // these devServer options should be customized in /config/index.js
  devServer: {
    clientLogLevel: 'warning',
    historyApiFallback: {
      rewrites: [
        { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') },
      ],
    },
    hot: true,
    //contentBase主要作用是如果我们打包后的资源,又依赖于其他的一些资源,那么就需要指定从哪里来查找这个内容,这里因为CopyWebpackPlugin会复制,所以不需要配置。
    contentBase: false, // since we use CopyWebpackPlugin.
    compress: true,
    host: HOST || config.dev.host,
    port: PORT || config.dev.port,
    open: config.dev.autoOpenBrowser,
    overlay: config.dev.errorOverlay
      ? { warnings: false, errors: true }
      : false,
    publicPath: config.dev.assetsPublicPath,
    proxy: config.dev.proxyTable,
    quiet: true, // necessary for FriendlyErrorsPlugin
    watchOptions: {
      poll: config.dev.poll,
    }
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': require('../config/dev.env')
    }),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
    new webpack.NoEmitOnErrorsPlugin(),
    // https://github.com/ampedandwired/html-webpack-plugin
    //自动生成index.html
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: 'index.html',
      inject: true
    }),
    // copy custom static assets
    //从static目录
    new CopyWebpackPlugin([
      {
        from: path.resolve(__dirname, '../static'),
        to: config.dev.assetsSubDirectory,
        ignore: ['.*']
      }
    ])
  ]
})

首先,将baseWebpackConfig中的配置进行了合并
然后,配置了devServer,配置了HtmlWebpackPlugin和CopyWebpackPlugin插件,这些前面都讲过。

看一下baseWebpackConfig

...
module.exports = {
  //entry的上下文路径,当前文件目录是build目录,'../'就是当前根目录
  context: path.resolve(__dirname, '../'),
  entry: {
    app: './src/main.js'
  },
  output: {
  	//打包路径 '../dist'
    path: config.build.assetsRoot,
    //打包生成的文件名
    filename: '[name].js',
    //打包的公共路径'/'
    publicPath: process.env.NODE_ENV === 'production'
      ? config.build.assetsPublicPath
      : config.dev.assetsPublicPath
  },
  /*resolve用来配置模块如何解析。例如,当在 ES2015 中调用 import 'lodash',resolve 选项能够
对 webpack 查找 'lodash' 的方式去做修改*/
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
     //这样导入vue/dist/vue.esm.js时,可以简化为import xx from vue,其中$表示精准匹配
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
    }
  },
  module: {
    rules: [
    //vue-loader解析.vue文件
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: vueLoaderConfig
      },
  ...
  ]
  }
  

build.js

const spinner = ora('building for production...')
spinner.start()

//删除path.join的目录,也就是/dist/static目录,如果删除成功,使用webpackConfig进行打包,webpackConfig就是webpack.prod.config.js输出的配置
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
  if (err) throw err
  webpack(webpackConfig, (err, stats) => {
    spinner.stop()
    if (err) throw err
    process.stdout.write(stats.toString({
      colors: true,
      modules: false,
      children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
      chunks: false,
      chunkModules: false
    }) + '\n\n')

    if (stats.hasErrors()) {
      console.log(chalk.red('  Build failed with errors.\n'))
      process.exit(1)
    }

    console.log(chalk.cyan('  Build complete.\n'))
    console.log(chalk.yellow(
      '  Tip: built files are meant to be served over an HTTP server.\n' +
      '  Opening index.html over file:// won\'t work.\n'
    ))
  })
})

6.其他文件说明
先看index.html,里面就有一个

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>vuecli-demo</title>
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

再来看入口文件./src/main.js,它是在webpack.base.config.js中配置的

import Vue from 'vue'
import App from './App' //引入了./App.vue
import router from './router' //引入了./router/index.js

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',//要渲染的的页面元素
  router, // 引用上面的router对象
  components: { App },//在Vue实例中定义局部组件App
  template: '<App/>' //在html页面中添加<App></App>模板,也就是使用局部组件App
})

使用局部组件也有一种简化的写法

new Vue({
    el: '#root',
    render: h => h(App)
})
//相当于
new Vue({
    el: '#root',
    template: '<App></App>',
    components: {
        App
    }
})

再来看一下App.vue文件,它其实就是一个组件,因为它里面有

,所以Vue实例使用这个组件时,会把index.html中的
替换掉

<template>
  <div id="app">
    <img src="./assets/logo.png">
    <router-view/>  <!--这里是vue-router的锚点,会自动将路径匹配的路由渲染到这里-->
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

继续看一下./src/router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'

Vue.use(Router)  //这里必须要有,否则router不生效

export default new Router({
	//定义路由的配置,当路径为'/'时将HelloWorld组件渲染进来
  routes: [
    {
      path: '/',
      name: 'HelloWorld',
      component: HelloWorld
    }
  ]
})

最后总结一下,首先Vue实例中定义了要渲染的标签,即id为app的标签,使用了局部组件app,局部组件中定义了一个img标签和路由的锚点,路由的路径为’/'时将HelloWorld组件渲染进来。所以,访问localhost:8080时,页面先展示了一张图片,再展示了HelloWorld组件中的内容。

参考文章:
Vue项目结构分析:https://blog.csdn.net/u010412833/article/details/112626070

4.@vue/cli

上述的vue-cli的版本是2.9.6,而vue-cli 3以后的版本在命令和脚本使用上与vue-cli 2有很大不同,下面简单介绍一下@vue/cli的使用

4.1 安装

#如果全局安装了vue-cli,要先npm uninstall vue-cli -g全局卸载掉后,再全局安装@vue-cli
npm install @vue/cli -g

安装完使用下面命令检查一下,注意V是大写

vue -V

4.2 使用

执行下面创建项目的命令

npx vue create vuecli3-demo

此时会不断出现选择提示
a.选择Manually select features,按回车
在这里插入图片描述

b. 选择babel、Router、Vuex(通过按空格键选择或者取消),按回车
在这里插入图片描述
c.选择2.x,按回车
在这里插入图片描述
d.Use history mode for router?输入y;
Where do you prefer placing…?选择package.json;
Save this as a preset for future projects?输入n(如果选y,表示把当前配置保存为preset预配置,后续创建project时可以直接使用这个preset,就不用一步步设置了)
在这里插入图片描述
配置完后按回车就开始安装了,安装完后进入项目根目录,执行命令npm run serve

cd vuecli3-demo
npm run serve

出现以下画面
在这里插入图片描述

4.3 配置文件解读

相比于vue-cli,@vue/cli的配置文件有了些许不同,并且,vue create构建出来的脚手架项目里的配置文件没有vue-cli清晰易读,本着打破砂锅问到底的精神,深挖一下配置,不感兴趣的可以跳过,有错误的地方也请指出
先看package.json

...
"scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build"
  },
...

可以看出,执行npm run serve时,实际上执行的是vue-cli-service serve命令

再看vue-cli-service
打开.\node_modules.bin目录下的vue-cli-service文件

#!/bin/sh
#获取当前文件所在目录
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")

case `uname` in
    *CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac

#如果当前路径有node,也就是本地安装了,使用本地的node执行,否则使用全局的node执行命令
if [ -x "$basedir/node" ]; then
  "$basedir/node"  "$basedir/../@vue/cli-service/bin/vue-cli-service.js" "$@"
  ret=$?
else 
  node  "$basedir/../@vue/cli-service/bin/vue-cli-service.js" "$@"
  ret=$?
fi
exit $ret

可以看出,最终使用node执行了…@vue\cli-service\bin下的vue-cli-service.js文件

再看vue-cli-service.js

//引入并创建了Service实例
const Service = require('../lib/Service')
const service = new Service(process.env.VUE_CLI_CONTEXT || process.cwd())

const rawArgv = process.argv.slice(2)
const args = require('minimist')(rawArgv, {
  boolean: [
    // build
    // FIXME: --no-module, --no-unsafe-inline, no-clean, etc.
    'modern',
    'report',
    'report-json',
    'inline-vue',
    'watch',
    // serve
    'open',
    'copy',
    'https',
    // inspect
    'verbose'
  ]
})
const command = args._[0]
//执行了service的run方法
service.run(command, args, rawArgv).catch(err => {
  error(err)
  process.exit(1)
})

可以看到,首先引入并创建了Service实例,然后执行了Service的run方法,所以我们需要看Service的run方法,看方法前,先看下Service的构造

Service的构造

constructor (context, { plugins, pkg, inlineOptions, useBuiltIn } = {}) {
    process.VUE_CLI_SERVICE = this
    this.initialized = false
    this.context = context
    this.inlineOptions = inlineOptions
    this.webpackChainFns = []
    this.webpackRawConfigFns = []
    this.devServerConfigFns = []
    this.commands = {}
    // Folder containing the target package.json for plugins
    this.pkgContext = context
    // package.json containing the plugins
    //解析package.json中的配置,比如入口main,plugins等
    this.pkg = this.resolvePkg(pkg)
    // If there are inline plugins, they will be used instead of those
    // found in package.json.
    // When useBuiltIn === false, built-in plugins are disabled. This is mostly
    // for testing.
    //解析plugins 
    this.plugins = this.resolvePlugins(plugins, useBuiltIn)
    // pluginsToSkip will be populated during run()
    this.pluginsToSkip = new Set()
    // resolve the default mode to use for each command
    // this is provided by plugins as module.exports.defaultModes
    // so we can get the information without actually applying the plugin.
    this.modes = this.plugins.reduce((modes, { apply: { defaultModes } }) => {
      return Object.assign(modes, defaultModes)
    }, {})
  }

可以看出,主要的是解析package.json和里面的plugins,看下解析plugins的方法

resolvePlugins (inlinePlugins, useBuiltIn) {
    const idToPlugin = (id, absolutePath) => ({
      id: id.replace(/^.\//, 'built-in:'),
      apply: require(absolutePath || id)
    })
    
    let plugins
	//默认的plugins
    const builtInPlugins = [
      './commands/serve',
      './commands/build',
      './commands/inspect',
      './commands/help',
      // config plugins are order sensitive
      './config/base',
      './config/assets',
      './config/css',
      './config/prod',
      './config/app'
    ].map((id) => idToPlugin(id))
  ...
   // Local plugins
   //pakage.json中定义的vuePlugins 
    if (this.pkg.vuePlugins && this.pkg.vuePlugins.service) {
      const files = this.pkg.vuePlugins.service
      if (!Array.isArray(files)) {
        throw new Error(`Invalid type for option 'vuePlugins.service', expected 'array' but got ${typeof files}.`)
      }
      plugins = plugins.concat(files.map(file => ({
        id: `local:${file}`,
        apply: loadModule(`./${file}`, this.pkgContext)
      })))
    }
  ...
    //排序
    const orderedPlugins = sortPlugins(plugins)
  ...
    return orderedPlugins

可以看到,定义了默认的plugins,也解析了pakage.json中定义的vuePlugins,最后做了排序。最终的plugins是一个由map组成的list,map的key是id,value是apply函数,如下

const idToPlugin = (id, absolutePath) => ({
      id: id.replace(/^.\//, 'built-in:'),
      apply: require(absolutePath || id)
    })

以默认的’./commands/serve’为例,id是’./commands/serve’, value是引入的./commands/serve.js中方法(下面会分析如何调用)

继续看Service.run方法

async run (name, args = {}, rawArgv = []) {
    // resolve mode
    // prioritize inline --mode
    // fallback to resolved default modes from plugins or development if --watch is defined
    const mode = args.mode || (name === 'build' && args.watch ? 'development' : this.modes[name])

    // --skip-plugins arg may have plugins that should be skipped during init()
    this.setPluginsToSkip(args, rawArgv)

    // load env variables, load user config, apply plugins
    //加载env变量,加载用户定义的config,执行plugins
    await this.init(mode)

    args._ = args._ || []
    let command = this.commands[name]
    if (!command && name) {
      error(`command "${name}" does not exist.`)
      process.exit(1)
    }
    if (!command || args.help || args.h) {
      command = this.commands.help
    } else {
      args._.shift() // remove command itself
      rawArgv.shift()
    }
    const { fn } = command
    return fn(args, rawArgv)
  }

可以看到主要执行的是this.init(mode)方法

继续看Service的init方法

init (mode = process.env.VUE_CLI_MODE) {
    if (this.initialized) {
      return
    }
    this.initialized = true
    this.mode = mode

    // load mode .env
    if (mode) {
      this.loadEnv(mode)
    }
    // load base .env
    this.loadEnv()

    // load user config
    //加载用户自己配置的./vue.config.js
    const userOptions = this.loadUserOptions()
    const loadedCallback = (loadedUserOptions) => {
      this.projectOptions = defaultsDeep(loadedUserOptions, defaults())

      debug('vue:project-config')(this.projectOptions)

      // apply plugins.
      //执行plugins
      this.plugins.forEach(({ id, apply }) => {
        if (this.pluginsToSkip.has(id)) return
        apply(new PluginAPI(id, this), this.projectOptions)
      })

      // apply webpack configs from project config file
      if (this.projectOptions.chainWebpack) {
        this.webpackChainFns.push(this.projectOptions.chainWebpack)
      }
      if (this.projectOptions.configureWebpack) {
        this.webpackRawConfigFns.push(this.projectOptions.configureWebpack)
      }
    }

    if (isPromise(userOptions)) {
      return userOptions.then(loadedCallback)
    } else {
      return loadedCallback(userOptions)
    }
  }

可以看到,主要干了两件事,一是加载用户自己配置的vue.config.js,二是执行plugins
首先加载用户定义的config,一般就是./vue.config.js,为什么是./vue.config.js呢?

loadUserOptions () {
    const { fileConfig, fileConfigPath } = loadFileConfig(this.context)

    if (isPromise(fileConfig)) {
     ...
        }))
    }

loadUserOptions 方法调用了@vue/cli-service/lib/util/loadFileConfig.js的loadFileConfig方法

module.exports = function loadFileConfig (context) {
  let fileConfig, fileConfigPath
  //定义了3个文件路径
  const possibleConfigPaths = [
    process.env.VUE_CLI_SERVICE_CONFIG_PATH,
    './vue.config.js',
    './vue.config.cjs',
    './vue.config.mjs'
  ]
  //遍历上面3个文件路径,取最先找到的那个,所以说一般就是./vue.config.js
  for (const p of possibleConfigPaths) {
    const resolvedPath = p && path.resolve(context, p)
    if (resolvedPath && fs.existsSync(resolvedPath)) {
      fileConfigPath = resolvedPath
      break
    }
  }

  //解析./vue.config.js
  if (fileConfigPath) {
    const { esm } = isFileEsm.sync(fileConfigPath)

    if (esm) {
      fileConfig = import(pathToFileURL(fileConfigPath))
    } else {
      fileConfig = loadModule(fileConfigPath, context)
    }
  }

  return {
    fileConfig,
    fileConfigPath
  }
}
module.exports = function loadFileConfig (context) {
  let fileConfig, fileConfigPath

  const possibleConfigPaths = [
    process.env.VUE_CLI_SERVICE_CONFIG_PATH,
    './vue.config.js',
    './vue.config.cjs',
    './vue.config.mjs'
  ]

然后执行plugins,就是循环plugins这个list中map的apply方法,以上文plugins中默认的’./commands/serve’为例,也就是调用./commands/serve.js中方法

最后看./commands/serve.js中方法

//api就是上文中的Service对象
module.exports = (api, options) => {
  const baseUrl = getBaseUrl(options)
  //向Service中注册了serve命令、参数和对应的执行函数(所以我们才能使用vue-cli-service serve命令来启动)
  api.registerCommand('serve', {
    description: 'start development server',
    usage: 'vue-cli-service serve [options] [entry]',
    options: {
      '--open': `open browser on server start`,
      '--copy': `copy url to clipboard on server start`,
      '--stdin': `close when stdin ends`,
      '--mode': `specify env mode (default: development)`,
      '--host': `specify host (default: ${defaults.host})`,
      '--port': `specify port (default: ${defaults.port})`,
      '--https': `use https (default: ${defaults.https})`,
      '--public': `specify the public network URL for the HMR client`,
      '--skip-plugins': `comma-separated list of plugin names to skip for this run`
    }
  }, async function serve (args) {
    info('Starting development server...')
    ...
    //引入了WebpackDevServer 
    const WebpackDevServer = require('webpack-dev-server')
    ...
    // resolve webpack config
    //解析webpack的config,都是一些基础配置,不是用户需要关注的
    const webpackConfig = api.resolveWebpackConfig()
    ...
    //加载用户通过vue.config.js配置的config,比如配置devServer:{port:8080}
    const projectDevServerOptions = Object.assign(
      webpackConfig.devServer || {},
      options.devServer
    )
    ...
    //解析最终的options,这里args.host就是用命令行传入的options,比如执行vue-cli-service serve --host --port,projectDevServerOptions.host就是上面vue.config.js配置devServer中的,可以看出,命令行配置优先于vue.config.js配置
    const host = args.host || process.env.HOST || projectDevServerOptions.host || defaults.host
    portfinder.basePort = args.port || process.env.PORT || projectDevServerOptions.port || defaults.port
    const port = await portfinder.getPortPromise()
    ...
    // create server
    //创建server
    const server = new WebpackDevServer(Object.assign({
    },...)
    ...
	return new Promise((resolve, reject) => {
	...
	//启动server
	  server.start().catch(err => reject(err))
    })

serve.js中方法主要是加载配置,创建server和启动server,其中配置的优先顺序是命令行参数>vue.config.js配置的devServer参数,比如在vue.config.js中配置devServer:{port:8080},在启动时执行npx vue-cli-service serve --port 8081,最终服务的端口为8081。
另外,server就是对webpackDevServer的封装,因此vue.config.js的devServer配置与webpack的配置文件webpack.config.js的devServer一样,其他配置也基本类似。

module.exports = {
    productionSourceMap: false,//生产环境是否要生成 sourceMap
    publicPath: './',//部署应用包时的baseURL,用法和 webpack 本身的 output.publicPath 一致
    outputDir: 'dist',//build 时输出的文件目录
    assetsDir: 'assets',//放置静态文件夹目录
    devServer: {
        port: 8090,
        host: '0.0.0.0',
        https: false,	//是否启用 https
        open: true //启动时是否直接打开浏览器
    },
    // 其他配置
    ...

至此@vue/cli的配置就分析完了

小结:@vue/cli的npm run serve命令实际上是执行vue-cli-service serve命令,vue-cli-service命令是执行vue-cli-service.js,创建了Service实例,Service实例调用了init方法,加载了vue.config.js配置文件,加载了’./commands/serve’, ‘./commands/build’,‘./commands/inspect’,'./commands/help’等plugin文件,注册了serve、build、inspect、help等命令,所以如果vue-cli-service serve命令会调用./commands/serve.js,创建并启动server,完成了服务的启动

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值