前端构建相关

1. husky

使用git提交代码时可使用husky关联相关的git hooks进行相应的处理。

安装:

使用npm:
npm install husky -D   (-D等价于--save-dev)

使用yarn:
npx husky-init && yarn  

package.json 添加 prepare 脚本,并且执行一次(上面使用npx husky-init则会自动添加prepare script):

npm set-script prepare "husky install"
npm run prepare

添加pre-commit hook钩子:

npx husky add .husky/pre-commit "npm test"
git add .husky/pre-commit

其中npm test即为pre-commit时执行的脚本,可根据具体需求改成自己的脚本;

npx命令会先去全局查找husky命令,全局没找到再去当前目录找,当前目录也没有则安装该模块, 后面是执行的shell

验证pre-commit:

git commit -m "test"

前面pre-commit添加的`npm test`脚本将在每次提交前执行。

注意:上面的是husky v7版本的步骤,v4版本有很大的区别。(区别可参考:https://typicode.github.io/husky/#/)

删除.husky里面的.gitignore文件

参考文档:https://github.com/typicode/husky#readme

添加commit-msg hook钩子: : 

npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'

 

2. commitlint

安装:

// 使用npm:
npm install --save-dev @commitlint/{config-conventional,cli}

// 使用yarn:(如果提示The engine "node" is incompatible with this module,可添加--ignore-engines忽略引擎)
yarn add -D @commitlint/{cli,config-conventional} --ignore-engines

安装常规配置和cli,windows下为:

npm install --save-dev @commitlint/config-conventional @commitlint/cli

 

配置commitlint文件

echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js

如果出错可能是系统原因,可参考这里面的写法:https://github.com/conventional-changelog/commitlint

验证:

git commit -m 'test'

会报错

subject may not be empty [subject-empty]

修改为:git commit -m 'feat: test'  验证通过。

commitlint规则为: type(scope?): subject 

scope可选,比如feat(blog): add comment section

通用的type可以是:

[
'build',
'chore',
'ci',
'docs',
'feat',
'fix',
'perf',
'refactor',
'revert',
'style',
'test'
];

参考文档:

https://github.com/conventional-changelog/commitlint

https://typicode.github.io/husky/#/?id=install-1

https://www.cnblogs.com/wangpenghui522/p/15518836.html

 

3. eslint

为了在项目统一编码规范,一般都会使用eslint保证代码风格和规范的一致性,主要用于js。

a. eslint安装:

 npm install eslint --save-dev

b. 初始化生成eslint配置文件,创建的时候会提示各种选项,可根据自己项目具体情况进行选择,然后生成基本的配置项。

./node_modules/.bin/eslint --init

生成的基本配置:

// 生成 .eslintrc.js 文件
module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    'plugin:vue/essential',
    'airbnb-base',
  ],
  parserOptions: {
    ecmaVersion: 'latest',
    parser: '@typescript-eslint/parser',
    sourceType: 'module',
  },
  plugins: [
    'vue',
    '@typescript-eslint',
  ],
  rules: {
    "semi": ["error", "always"],
    "quotes": ["error", "single"],
    // allow console
    'no-console': 0,
  },
};

如果想忽略部分文件校验,可添加.eslintignore文件,配置规则和.gitignore规则一致。

生成的配置vue默认使用的是plugin:vue/essential ,这里推荐修改为plugin:vue/recommended,因为"plugin:vue/essential" 仅包含Base Rules和Priority A:Essential,"plugin:vue/recommended" 包含Base Rules、Priority A:Essential、Priority B: Strongly Recommended、Priority C: Recommended。

对于更多vue lint可参考:https://eslint.vuejs.org/,支持对vue文件里html进行校验。

修改后的配置:

module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    'airbnb-base',
    'plugin:vue/recommended',
  ],
  parserOptions: {
    ecmaVersion: 'latest',
    parser: '@typescript-eslint/parser',
    sourceType: 'module',
  },
  plugins: [
    'vue',
    '@typescript-eslint',
  ],
  rules: {
    "semi": ["error", "always"],
    "quotes": ["error", "single"],
    'no-console': 0,
    "vue/html-closing-bracket-newline": ["error", {
      "singleline": "never",
      "multiline": "never"
    }],
    'vue/html-closing-bracket-spacing': ['error', {
      'startTag': 'never',
      'endTag': 'never',
      'selfClosingTag': 'always'
    }],
    'vue/attributes-order': ['error', {
      'order': [
        'OTHER_ATTR',
        'LIST_RENDERING',
        'CONDITIONALS',
        'RENDER_MODIFIERS',
        'GLOBAL',
        'UNIQUE',
        'TWO_WAY_BINDING',
        'EVENTS',
        'CONTENT',
        'DEFINITION'
      ]
    }],
  },
};

在项目配置了commitlint/lint-stage进行校验时第一次有可能会报错,这时可在package.json配置script手动执行一次校验,再提交就没问题了。

 "scripts": {
    "eslint": "eslint --ext .js,.vue dir目录 --fix"
  },

参考:

https://zhuanlan.zhihu.com/p/104032620

https://eslint.vuejs.org/rules/

 

 

c. 命令行运行ESLint检测文件:

./node_modules/.bin/eslint yourfile.js

 

这时基本的eslint已经生效了,可在build或提交代码时统一进行验证,提交代码可配合husky的pre-commit勾子进行处理(可防止没自动eslint --fix就提交代码的情况), 

同时可配合lint-staged进行使用,

安装lint-staged:npm install lint-staged --save-dev

// package.json配置文件
"scripts": {
    "pre-commit-lint": "lint-staged",
 },
"lint-staged": {
    "*.js": [
      "eslint --fix"
    ],
    "*.vue": [
      "eslint --fix",
      "stylelint --fix"
    ],
    "*.{scss,css}": [
      "stylelint --fix"
    ]
},

husky(7.x版本)的pre-commit文件执行:npm run pre-commit-lint;

在git commit 时会触发pre-commit 执行package.json里的pre-commit-lint脚本,进行eslint --fix/stylelint --fix 

如果全部fix成功则会继续提交,否则则会提示错误信息提交失败。

在这里注意点,这里脚本执行stylelint --fix 提示需要安装postcss-html,但是手动触发--fix是不需要安装的 ,可以先安装下postcss-html

可参考https://segmentfault.com/a/1190000039880312

如果要在开发的过程中就进行验证提示,需要编辑器安装eslint插件(可能编辑器项目需要重新打开才生效)。

如果要在代码保存的时候自动进行fix (部分eslint),需要配合编辑器配置,比如vscode:

{
  // 禁用编辑器自带的格式化
  "editor.formatOnSave": false,
  "editor.codeActionsOnSave": {
    // 自动fix eslint
    "source.fixAll.eslint": true,
  },
}

 

d. 对于ts项目需要安装ts相关的插件

  parser使用@typescript-eslint/parser。plugins使用@typescript-eslint。extends使用'plugin:@typescript-eslint/recommended'和'airbnb-typescript/base',
  如果项目里既有js模块也有ts模块,eslint需要对js和ts配置不同的parser/plugins/extends。这里可以在eslintrc.js里面添加overrides属性进行配置。
  
module.exports = {
  env: {
    browser: true,
    node: true,
    // jest单元测试使用
    jest: true,
  },
  extends: [
     'airbnb-base',
  ],
  overrides: [
    {
      // ts文件过滤
      files: ['*.ts'],
      parserOptions: {
        parser: '@typescript-eslint/parser',
        // 指定ts配置文件
        project: ['./tsconfig.json'],
      },
      plugins: [
        '@typescript-eslint',
      ],
      extends: [
        'plugin:@typescript-eslint/recommended',
        'airbnb-typescript/base',
      ],
    },
  ],
......

 

 

参考文档:

http://eslint.cn/docs/user-guide/getting-started

http://eslint.cn/docs/developer-guide/shareable-configs

http://eslint.cn/docs/rules/

 

4. stylelint

为统一样式规范,使用stylelint进行处理。

a. 安装:

// 安装stylelint和stylelint标准配置
npm install --save-dev stylelint stylelint-config-standard

b. 添加配置文件

创建.stylelintrc.json或.stylelintrc.js配置文件,添加基本配置:

// 使用stylelintrc.json
{
  "extends": "stylelint-config-standard"
}

// 使用stylelinttr.js
module.exports = {
  "extends": [
    "stylelint-config-standard",
  ],
};

c.命令行检测

npx stylelint xx.css

 如果要在编辑css代码的时候进行提示,需要配合编辑器stylelint插件进行处理,编辑器安装stylelint插件。

 如果编辑的文件是vue等格式文件,需要在编辑器配置下,比如vscode配置文件里面需要添加

"stylelint.validate": [
    "css",
    "less",
    "postcss",
    "scss",
    "vue",
    "sass"
]

如果要在保存代码时自动fix,可配合编辑器使用,比如vscode配置文件里面需要添加

"editor.formatOnSave": false,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true,
    "source.fixAll.stylelint": true,
  },

如果安装的stylelint包版本大于或等于v14.0.0 ,则在校验vue等格式文件时会提示 Unknown word (CssSyntaxError)Stylelint(CssSyntaxError),而且不能自动fix错误,

这时需要安装 postcss-html或者stylelint-config-html或者stylelint-config-recommended-vue ,如果是验证vue文件推荐使用stylelint-config-recommended-vue。

stylelint配置文件可添加stylelint-config-recommended-vue (只有stylelint>=14.0.0才需要)

module.exports = {
  "extends": [
    "stylelint-config-standard",
    "stylelint-config-recommended-vue",
  ],
};

 发现vue文件也ok了,

如果要忽略相关文件验证可添加.stylelintignore文件,规则和.gitignore一致。

如果要在build或提交代码时进行验证 ,可配合husky的pre-commit勾子进行处理。可配合lint-staged进行使用,可参考上面的eslint处理。

 

参考文档:

https://stylelint.io/user-guide/get-started

https://github.com/stylelint/stylelint/issues/5634

https://github.com/ota-meshi/stylelint-config-recommended-vue

https://blog.csdn.net/qq1014156094/article/details/122456439

 

5. prettier

prettier主要用于代码的格式化,配置比较少,支持多种语言和编辑器,和eslint有点类似,但是主要功能是代码格式化,eslint主要按配置规则进行代码检测和格式化,两者有很多重复的功能。

前端使用eslint基本可满足了。

两者区别可参考:

https://www.zzjtnb.com/blog/details/prettierheslintdobjpz

 

6. Browserslist

在babel/postcss等打包时可以根据Browserslist配置的支持的浏览器信息来进行相应的处理。

比如某些高级语法是否需要转为es5(配置的浏览器都支持则不用转es5) ,是否需要添加polyfills(配置的浏览器都支持则不用添加polyfills)。

可在.browserlistrc文件或package.json文件配置。

// .browserlistrc文件

#支持各类浏览器最近的一个版本
last 1 version
#支持市场份额大于 1% 的浏览器
> 1%
#非最新的两个版本中其市场份额已低于0.5%且24个月内没有官方支持和更新
not dead

// package.json文件
"browserslist": [
    "last 1 version",
    "> 1%",
    "not dead"
  ],

  

参考文档:

https://github.com/browserslist/browserslist

https://aaronflower.github.io/essay/Browserslist.html

 

7. rollup

rollup适合对插件进行打包 ,优势是rollup 静态分析代码中的 import,并将排除任何未实际使用的代码。即使全量引入了模块在编译后的输出代码中,简单地运行自动 minifier 检测和去掉未使用的变量,不会像webpack那样打包文件包裹了webpack自己实现的模块加载代码,打包代码更简洁。

主要关注下plugin和output

目前, npm 中的大多数包都是以 CommonJS 模块的形式出现的,引入时必须引入完整的库对象,如何导出成你可以正常导入的 ES6 模块 (按需引入),这里需要使用@rollup/plugin-commonjs插件来操作把commonjs转换成es6模块。

需要注意的是这个转换插件必须用在其他插件转换模块之前 - 这是为了防止其他插件的改变破坏 CommonJS 的检测。这个插件还需要配合babel来进行处理,

babel里面设置modules: false(防止在 Rollup 处理前,babel把我们的模块转译为 CommonJS 风格)。

为了将前端代码打包成es5等需要使用babel插件@rollup/plugin-babel ,babel插件建议放到所有插件最后。

babel({
    // 编译排除node_modules,如果不需要编译可排除
    // exclude: /node_modules/,
    // use 'runtime' especially when building libraries with Rollup, It has to be used in combination with @babel/plugin-transform-runtime
    // https://github.com/rollup/plugins/tree/master/packages/babel
   // 允许 Rollup 在 bundle 头部仅包含一次 'helpers' ,而不是在用到的每个。模块都包含(需配合babel的@babel/plugin-transform-runtime插件使用)
    babelHelpers: 'runtime',
    // 使用根目录通用babel配置
    configFile: path.resolve(__dirname, '../babel.config.js'),
  }),

 

babel配置:

module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
        // 库的编译不需要引入polyfill https://cli.vuejs.org/zh/guide/browser-compatibility.html#usebuiltins-usage
        // https://babel.docschina.org/docs/en/babel-preset-env/
        useBuiltIns: false,
        // preserve transformation of ES module syntax to another module type
        modules: false,
      },
    ],
  ],
  plugins: [
    [
      '@babel/plugin-transform-runtime',
    ],
  ],
};

 

把json文件转为es6模块需要使用@rollup/plugin-json插件。
如果代码加载了第三方模块还必须使用@rollup/plugin-node-resolve,用来找出和打包第三方安装在node_modules里面的模块。
打包前需要清空目录可使用rollup-plugin-clear插件,
const plugins = [
  // Converts .json files to ES6 modules
  json(),
  // convert CommonJS modules to ES6,应该用在其他插件转换模块之前 - 这是为了防止其他插件的改变破坏 CommonJS 的检测
  commonjs(),
  // Locate and bundle third-party dependencies in node_modules
  nodeResolve(),
  clear({ targets: ['lib'] }),
];

在打包时我们希望第三方引入的包不要打包进我们自己的模块里面,可设置external,如果要将所有node_modules插件排除在外,可以使用rollup-node-external插件(看github已不维护了)

压缩代码可使用rollup-plugin-terser插件

如果打包目标为umd模块,必须设置模块名name,  exports设置为'auto'

如果需要同时进行多个模块打包,同时输出多个模块,在rollup.config.js模块导出数组配置

// 单个配置
export default configs;

// 多个配置
export default [
  config1,
  config2,
  ......
];

 

参考:

https://www.rollupjs.com/

https://www.kancloud.cn/yunye/rollup/327468

 

8. lerna

一个javascript monorepo管理工具,npm7.x和yarn等包管理工具支持workspace进行多package的依赖管理, 相比起来lerna api使用起来更方便,且兼容npm7.x以下版本。

a. lerna init 初始化项目

Lerna 提供两种不同的方式来管理你的项目:Fixed 或 Independent,默认采用 Fixed 模式,
如果你想采用 Independent 模式,只需在执行 init 命令的时候加上 --independent 或 -i 参数即可

When using the fixed mode, all the packages will be published using the same version.
When using the independent mode, every package is versioned separately,

或直接修改lerna.json文件"version": "independent"

b. 使用workspace 

可在lerna.json里面配置 "useWorkspaces": true,并且在根目录package.json里面配置Workspaces目录。

也可不用workspace ,直接用lerna自带的packages目录管理,实现了类似workspace的子包依赖管理。这时需在lerna.json里面配置包目录。

"packages": [
"packages/*",
],
c. lerna bootstrap
安装所有package包里面的依赖项以及包之间的软链依赖, 根目录依赖需npm install安装。
默认是使用npm进行包管理,如果要修改,可修改lerna.json配置

{
"npmClient": "xxx",
"npmClientArgs": ["--ignore-prepublish"]
}

lerna bootstrap --hoist [glob]在项目根目录安装公共依赖,自动检测package中相同的依赖,并将其安装在根目录下的node_modules中,减少依赖安装次数,提升速度,

各个子包的node_modules .bin里面存在一个环境变量指向根目录node_modules。

注意,如果是用npm打包,npm7.0以上才支持workspace,
如果npm版本低于7.0且用的npm打包,这时执行lerna bootstrap会提示:
lerna info bootstrap root only ,只会安装根目录依赖
解决方案i: 升级npm到7.0以上;
解决方案ii: 使用yarn等npmClient;
解决方案iii: lerna.json里面配置 "useWorkspaces": false,(可不配置默认为false)

如果使用workspace ,需在lerna.json配置:
{
"useWorkspaces": true
}
这样根目录的package.json文件就需要配置"workspaces": ["packages/*"],并且会覆盖lerna.json配置的packages
其中lerna.json配置的packages支持递归通配符匹配“**”,而使用workspace在package.json配置的packages是不支持的。

d. lerna create 添加包

lerna create <name> [loc]

name 是模块的名称(必填项,可包含作用域,如 lerna create @xxx/module-a addr,后面的loc地址为addr目录
如果包含作用域必须在workspace或lerna.json指定作用域,比如: [
"packages/xxx/*",
],)

也可手动添加包文件,包里面执行npm init

对于使用模板创建包,lerna 暂时不支持该功能,lerna github issue里面有人提了这个问题,lerna的开发建议使用npm init <initializer>自己去实现。

npm init <initializer>对应命令:npx create-<initializer>。

npx是什么:
参考:https://www.ruanyifeng.com/blog/2019/02/npx.html

npx会到项目根目录node_modules/.bin目录去查找有没有该命令(create-<initializer>),有则执行这个命令,没有则下载这个包(包名字为initializer,注意没有create关键字)到临时目录,执行命令后再删除该包(虽然删除但是本地还是会存在缓存)。

node_modules/.bin下的命令其实就是对应安装包package.json文件里面bin属性里面的命令(create-<initializer>)的一个软链接,最终执行的是该bin属性里面的命令。
安装包会不会在node_modules/.bin目录下生成软链接命令取决于该包package.json里面的bin属性是否有命令配置。

比如http-server的package.json文件包含了http-server命令,执行该命令就会执行该包的bin/http-server文件
"bin": {
"http-server": "bin/http-server"
},
注意,create和innit为init的别名,所以npm init也可使用npm create 和npm innit

如果是yarn进行包管理的,需要使用yarn create, 我们可以看到create-react-app是使用这种方式创建的https://github.com/facebook/create-react-app
但是vite无论是npm还是yarn等都是使用的create ,应该是为了统一,比如npm create / yarn create 创建项目:https://github.com/vitejs/vite/tree/main/packages/create-vite
npm init api 参考:https://docs.npmjs.com/cli/v8/commands/npm-init

npm init <initializer>
安装时如果本地有之前安装过的缓存,这时如果要安装最新版本必须指定@latest, 比如:
npm create vite@latest
不要使用--force强制下载安装,会提示protections disabled
npm WARN using --force Recommended protections disabled.

e. 运行packages里面的任务:

lerna run build ,运行所有子包里面的build脚本(如果package.json script有build才会执行)
lerna run build --scope=header 运行指定package任务

f. 发布到npm
lerna publish --no-private (发布非private的包,本地没提交的版本)
发布包含作用域@的包,比如@xxx/http,需在package.json添加:
"publishConfig": {
"access": "public"
}

如果不包含作用域则不用设置

如果代码已提交到远程,可使用lerna publish from-git # 发布当前提交中标记的包(发布最新版本包)
lerna publish from-package # 发布注册表中没有最新版本的包(有几个版本没发布就会发布几个版本)
lerna publish会先触发lerna version ,为了使用version的参数配置,最好先lerna version xxx再lerna public from-git

如果想发布某个子包,可以进入到子包目录执行原生的npm publish,lerna不支持指定子包发布。

发布时需要先npm login登录下。

g. lerna changed
List local packages that have changed since the last tagged release,列出自上次发布后更改的包。

h. lerna diff
Diff all packages or a single package since the last release.

i. lerna exec
Execute an arbitrary command in each package

j. lerna run
Run an npm script in each package that contains that script
lerna run test (全部模块)
lerna run --scope my-component test (my-component模块)
lerna run build --npm-client=yarn 默认为npm

k. lerna clean
Remove the node_modules directory from all packages.
does not remove modules from the root node_modules directory,

l. lerna add 给包添加依赖包
lerna add <module name> [--scope=<module name>]
Add a dependency to matched packages
lerna add module-1 --scope=module-2 (Install module-1 to module-2)
lerna add module-1 (Install module-1 in all modules except module-1)
lerna add babel-core (Install babel-core in all modules)

--scope后面是模块名字

m. lerna version

对包打版本。

由于lerna version会默认提交push到远程仓库,如果我们不想让它自动git push,可以执行lerna version --no-push,之后让选择各个 package 的版本号,也可选择自己输入版本。

lerna version --conventional-commits 将使用conventionalcommits.org/en/v1.0.0/提交规范确定版本并生成 CHANGELOG.md文件
设置了conventional-commits 就会根据提交信息自动打版本号(否则会让选择版本号),不过这个自动打版本感觉有点问题,只遵循了部分conventionalcommits版本规范。

生成 CHANGELOG.md文件每次都是往上面累加的,也可以对该文件进行修改再提交。

lerna version --amend 将对当前提交执行所有更改,而不是添加一个新的,可以减少项目历史记录中的提交数量,这个命令将跳过git push(也就是说--no-push) ,
但是除了当前提交,还会要求pull merge代码生成一个新的空的合并提交记录。
可以使用--no-push来阻止自动push

如果修改了包名字,由于umd会依赖包名字,这时打包会重新生成打包的文件,而且changelog会根据新包名字重新生成log(自动替换旧包名字的changelog,之前的log看不到了),尽量不要修改包名字。

打版本也可使用npm自带的功能,npm version 

 

9. npm 

a. nrm镜像管理使用教程
安装:
npm install -g nrm
使用:
nrm ls
nrm use npm
nrm add <registry> <url> nrm add company http://npm.company.com/
nrm del <registry> nrm del company
nrm test npm (测试速度)
nrm test
nrm home taobao
b. 原生npm命令
npm config get registry (获取设置的registry)
npm config set registry https://registry.npm.taobao.org/ (设置registry)
npm install --registry=https://registry.npm.taobao.org (安装包使用特定registry)
参考:https://segmentfault.com/a/1190000017419993

c. npm run

npm run如果不加任何参数,直接运行,会列出package.json里面所有可以执行的脚本命令(script字段里面的内容)。

执行原理:
使用npm run script执行脚本的时候都会创建一个shell环境,然后在shell中执行指定的脚本。
这个shell会将当前项目的可执行依赖目录(即node_modules/.bin)添加到环境变量path中,当执行之后再恢复原样。
就是说脚本命令中的依赖名会直接找到node_modules/.bin下面的对应脚本,而不需要加上路径,所以scripts字段里面调用命令时不用加上路径,这就避免了全局安装NPM模块
npm执行顺序:

'&' 并行执行顺序,同时执行(并行)
"dev":"node test.js & webpack"

'&&'继发顺序,执行前面之后才可以执行后面 (串行)
"dev":"node test.js && webpack"

生命周期钩子(pre、post):
"predev":"node test_one.js",
"dev":"node test_two.js",
"postdev":"node test_three.js"
在命令名字前加上生命周期钩子,当执行 npm run dev 的时候默认就会执行 npm run predev && npm run dev && npm run postdev

四个可以简写的脚本执行命令:
npm start === npm run start
npm stop === npm run stop
npm test === npm run test
npm restart === npm run stop && npm run restart && npm run start

使用package.json内部变量:
通过npm_package_<key>,npm脚本可以拿到npm的内部变量
// package.json
{
"name":"testname",
"test":"node test.js"
}

test.js:
console.log(process.env.npm_package_name) //testname
参考:https://www.programminghunter.com/article/7967337677/

 

10. pnpm (performance npm 高性能npm)

npm3.0以前安装包是已树形结构进行安装,存在重复包重复安装(整体安装速度/磁盘空间占用问题),windows下包嵌入太深名字太长问题 

npm3.0以后以及yarn 实现了安装包的提升,将树形依赖的包提升到顶层(扁平化),但是对于一个相同包多个不同版本,只能提升一个版本(安装顺序决定提升哪个版本),其他版本依然会重复嵌套安装,场景在monorepo多包场景下尤其明显,这也是yarn workspace经常被吐槽的点,另外

提升到顶层会导致没有在package.json依赖项里面的包也能直接在代码访问(幻影依赖),导致一些问题比如引用的包忘记添加到依赖项了。

pnpm使用store和link方式解决了重复安装的问题,以及幻影依赖问题。提升了安装速度,节省了磁盘空间,包依赖和引入更加严格,
只有项目package.json里面依赖的安装包才会在node_modules子目录扁平化展示(依赖包的依赖会在.pnpm目录体现),但里面的都是软链,真正的包文件都在node_modules/.pnpm目录下,而.pnmp目录里又是使用的硬链接指向了电脑磁盘的包目录(store),保证了所有项目对于相同的包共用一个实体包文件,在项目中通过硬链接的形式使用。在.pnpm所有子依赖包会提升硬链接指向store。由于node_modules子目录依赖包不存在提升,所以不存在幻影依赖问题。

比如项目有foo包,依赖了bar包,安装后整体结构如下:

node_modules
├── foo -> ./.pnpm/foo@1.0.0/node_modules/foo
└── .pnpm
├── --bar@1.0.0
│ └── --node_modules
│ └── --bar -> <store>/bar
└── --foo@1.0.0
└── ----node_modules
├── -----foo -> <store>/foo
└── -----bar -> ../../bar@1.0.0/node_modules/bar

硬链接:和源文件内容是同步的,公用一个inode(索引节点),就像一个文件有多个名字,可以认为是同一个文件(平等关系),文件显示大小和源文件是一样的,不支持跨文件系统建立,
不同的是硬链接和源文件所属的目录不同,使用不同的目录项来存储两个文件名等信息,
只有所有名字(硬链接)都删除了,该文件才真正删除(inode和对应block区域即实际内容被回收),删除一个硬链接源文件没任何影响,硬链接只能链接文件不能链接到目录,

通常情况下,设置硬链接文件时,认为不会占用磁盘空间和inode。
新建一个硬链接只是在某个目录(硬链接所在的当前目录)下的block里多写入一条关联数据而已。(硬盘最小存储单位扇区,一次读取一个block块(8个扇区))
只有在当前目录的block刚好填满时,才有可能为当前目录新增一个block来记录文件名和inode的关联记录
因为硬链接所用掉的关联数据量很小,所以说硬链接通常不会改变inode和磁盘空间大小,其实还是占一点点磁盘空间的。

文件数据都储存在"块"中,那么很显然,我们还必须找到一个地方储存文件的元信息,
比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,中文译名为"索引节点"。

项目npm升级为pnpm步骤:

a. pnpm import 根据package-lock.json或yarn.lock生成pnpm-lock.yaml文件, 删除旧的lock文件
b. 如果有lerna.json,修改lerna.json为"npmClient": "pnpm",
c. 开启npm的pre/post添加.npmrc配置文件,设置enable-pre-post-scripts=true或全局设置pnpm config set enable-pre-post-scripts true
d. npm/yarn命令改成pnpm
e. 删除node_modules, 重新安装依赖, 打包过程提示找不到模块的重新安装即可
f. 所有的peerdependce需要手动安装

g. eslint处理,'import/no-extraneous-dependencies': ['error', { devDependencies: ['**/*.js', '**/*.ts'] }], 等。

参考:

https://blog.csdn.net/weixin_39969298/article/details/116804602

https://www.ruanyifeng.com/blog/2011/12/inode.html

https://www.zhihu.com/question/20729978
https://www.zhihu.com/question/66578656

https://pnpm.io/zh/motivation

 

 

posted @ 2022-01-09 22:38  lmh2072005  阅读(520)  评论(0编辑  收藏  举报