Win平台软件技巧01

windows平台 软件技巧

安装gitbash

  • win平台弱化了程序员的命令行能力,所以要多学习linux命令
  • 安装 阉割版的 gitbash 保证你能使用基本的 常用 shell
  • 安装gitbash参考此文章即可

gitbash 骚操作

gitbash 如何复制文本

  • 复制:bash框顶部 右键 选择 options 选择 Mouse -> 选择 Copy on select
  • 粘贴:鼠标中间代表 顶栏 右键 options Mouse -> Middle mouse button 选择 Paste

快速跳转 曾经去过的目录 j命令

  • step01 github 搜索 “z” 找到 https://github.com/rupa/z
  • step02 打开 gitbash 切换到用户目录 cd ~
    • ~ 代表 c/User/"你wind平台电脑的用户名"
  • step03 cd ~ 后新建一个目录 mkdir demorepos 克隆z仓库代码 到此目录
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cd ~
mkdir demorepos
cd demorepos
# ssh 和 https 或者 下载zip包都可以 下载到 ~/demorepos 目录里
git clone git@github.com:rupa/z.git

# 下载完成后 demorepos 出现了个 z 目录, z目录里 有个 z.sh
cd z

# 获取 z.sh 的目录
pwd
c/Users/hjx/demorepos/z
# 补全这个目录
c/Users/hjx/demorepos/z/z.sh
```
- step04 保存上面的 z.sh 的命令路径做 别名映射

vi ~/.bashrc

添加一行

. ~/demorepos/z/z.sh

保存退出

然后你去过的任何目录 都可以被记录下来

1
2

- step05 验证

cd ~/Desktop
cd /c
cd /d
cd ~/

验证

输入 z 回车,出现所有你去过的目录

z

模糊跳转

z d # 跳转到 /d 目录
z Des # 跳转到 ~/Desktop

1
2

- step06 我要的是 "j" 而不是 "z"

继续编辑 ~/.bashrc

完整内容如下

. ~/demorepos/z/z.sh

用 j 代替 z

alias j=’z’

保存退出

让bash生效两种方式

  • 退出bash 重新 gtibash here
  • source ~/.bashrc
    1
    2
    3
    4

    ## alias 技巧

    ### mac 的 `open .` 代表打开当前目录,但是win平台没有open命令它有个类似的叫做 `start .`

. ~/demorepos/z/z.sh

alias j=’z’
alias gi=”git init”
alias gst=”git status -sb”
alias ga=”git add”
alias gcv=”git commit -v”
alias gcm=”git commit -m”
alias gp=”git push”
alias gl=”git pull”
alias open=”start”
alias ll=”ls -la”
alias glog=”git log –graph –pretty=format:’%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset’ –abbrev-commit – | less”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

## bash美化

### 窗口大小调整

- 拖拽bash窗口到你想要的尺寸
- gitbash 顶部右键 options
- 选中 window => current size

### 字体设置

- https://github.com/powerline/fonts/tree/master/SourceCodePro 挨个下载 下载完成后点击文件 提示你安装
- 我这里偷懒了 我就 gitbash 顶部右键 options
- text => select 选的 "Consolas"
- text => show bold as font 勾选
- text => show bold as color 勾选 ,选中Default

### 字符集问题

- 默认的gitbash `ping baidu.com` 显示为乱码
- 通过 顶栏 右键 options
- text => character set 选 utf-8 `ping baidu.com` 还是乱码
- 但是你用 curl -L baidu.com 就不是乱码
- ping 命令是win平台的 命令,win命令默认是 "gbk"
- 推荐字符集设置为 utf-8

## curl命令

- step01 打开 [scoop.sh](https://scoop.sh/)
- step02 windows的 powershell 打开运行`Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')`
- 如果报错 建议安装 ss开启 全局模式 代理
- step03 gitbash 里 `which scoop` 显示出路径代表安装成功
- step04 gitbash里 `scoop install curl`

## tree命令

- google [gnutree](http://gnuwin32.sourceforge.net/packages/tree.htm)
- 下载 完整版 Complete package setup文件,然后一路next
- 下载后安装在 `C:\soft\GnuWin32`
- gitbash里 `cd c/soft/GnuWin32/bin` ls 发现有 `tree.exe`
- `vi ~/.bashrc`
添加一句

export PATH=”$PATH:/c/soft/GnuWin32/bin”

1
2
3
4
5
6
7
8
9
10

## 翻译工具

- 前提安装 node / npm
- `npm i -g fanyi`
- 验证`fy hi`

### 如果命令不生效
- 如果提示 commond not found,就需要更新 PATH
- `.bashrc`里添加

export PATH=”c/Users/你的win用户名/AppData/Roaming/npm:$PATH”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

## win gitbash常用快捷键

- Ctrl + R 搜索历史,再次 Ctrl + R 切换
- Ctrl + A 行首
- Ctrl + E 行尾
- Ctrl + B 退后
- Ctrl + F 前进
- Ctrl + W 删一个单词
- Ctrl + Kill 干掉这一行
- Ctrl + Cancel 中断
- Ctrl + Go 退出搜索

### fzf

- `scoop install fzf`

查找文件

f() {
local file
q=$1

file=$( fzf –query=”” –select-1 –exit-0 -x)
if [ -n “$file” ] ;then
¦ vim “$file”
fi
echo “fzf: bye”
}

查找并 open 文件,如果你没有 open 请先 alias 一下 open

fo(){
local file
q=$1

#file=$(ag -l -g “”| fzf –query=”$q” –select-1 –exit-0 -x)
file=$( fzf –query=”” –select-1 –exit-0 -x)
if [ -n “$file” ] ;then
¦ open “$file”
fi
echo “fzf: bye”
}

查找并打开文件所在目录

fd() {
local file
local dir
file=$(fzf +m -q “$1”) && dir=$(dirname “$file”) && cd “$dir”
}

搜索文件内容并用vim打开对应行

fs(){
local file
q=$1
if [ -z “$q”] ;then
¦ q=”.”
fi
result=$(ag “$q” | fzf)
IFS=’:’ read file line other <<< “$result”
[ -n “$file” ] && vim “$file” +”$line”;
}
`

Webpack-03loader

loader

如何加载css文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 收集依赖读取代码 看见 import/require 的时候
const code = readFileSync(filepath).toString()

// 重点:正则判断
if(/\.css$/.test(filepath)){
// 此时 css文件的内容 变成了 js的一个字符串
code = `
const str = ${JSON.stringify(code)}
if(document){
const style = document.createElement('style')
style.innerHTML = str;
document.head.appendChild(style)
}
export default str;
`
}

loader 可以是一个函数 or 异步函数

1
2
3
4
5
6
7
8
9
10
11
12
function transform(code){
const code2 = doXXX(code)
return code2
}
module.exports = transform

// or
async function transform(code){
const code2 = await doXXX(code)
return code2
}
module.exports = transform
  • css-loader.js
1
2
3
4
5
6
7
8
9
10
11
12
function transform(code){
return `
const str= ${JSON.stringify(code)}
if(document){
const style = document.createElement('style')
style.innerHTML = str;
document.head.appendChild(style)
}
export default str;
`
}
module.exports = transform
  • 核心逻辑
    • 为啥用 require 加载loader,因为webpack的loader是从 config里读取的,要动态的加载
    • 第二个原因是 旧版的node不支持 import
1
2
3
4
if(/\.css$/.test(filepath)){
// 此时 css文件的内容 变成了 js的一个字符串
code = require('./loaders/css-loader.js')(code)
}

loader的优化

  • 单一职责原则
  • 每个loader只做一件事

大概流程

  • sass-loader 将 .scss 文件转化为 .css
  • css-loader 将 .css 文件转化为 js 里的一个变量 字符串str
  • style-loader 通过 生成style标签 将 str 内容插入到 style里
    • 本质是在 原有代码基础上 插入一部分代码
    • 这个时机在哪?这个要参考webpack

webpack style-loader思路

  • style-loader 在 pitch 钩子里通过 css-loader 来require 内容
  • 然后

webpack-loader 是什么

  • webpack自带的打包只支持 js
  • 当我们想要加载 css/scss/ts/md 文件时,需要用loader
  • loader 原理就是把文件内容包装成能运行的JS

例如

  • 加载 css 用到 style-loader 和 css-loader
  • css-loader 把代码 从 CSS 变成 export default str 形式的JS
  • style-loader 把代码挂在到 head 里的 style 里

例如

  • file-loader 如果是 图片,图片小就变成 base64,图片大就在 形如public 的资源目录里生成这个文件,然后读取这个文件的相对路径

Webpack-02打包器

Webpack解决那些问题

  • 浏览器不支持 import/export,因为浏览器里这样运行不了
  • 代码仓库
  • 安装好依赖

怎样解决这个问题

不同浏览器功能不同

  • 现代浏览器可以通过<script type="module"> 来支持 import/export
  • 但是 IE8~15 不支持

兼容策略

  • 激进策略:所有代码放到 <script type="module">
    • 缺点:
      • http请求文件过多
      • 不被IE支持
  • 平稳兼容:把关键字转译为普通代码,并把所有文件打包为一个文件
    • 缺点:需要写复杂的代码,完成这个事。这就是我们的任务

解决问题1: import/export 转化成函数

  • 通过@babel/preset-env 把ES6 转化为 ES5
  • bundler_1.ts
1
2
3
4
5
6
7
const code = readFileSync(filepath).toString()
// step01 code 不要原封不动的放到 这里,而是变成es5
const { code: es5Code } = babel.transform(code, {
presets: ['@babel/preset-env']
})
// 初始化 depRelation[key]
depRelation[key] = { deps: [], code: es5Code }
  • 运行node -r ts-node/register bundler_1.ts 后打印 depRelation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
{
'index.js': {
deps: [ 'a.js', 'b.js' ],
code: '"use strict";\n' +
'\n' +
'var _a = _interopRequireDefault(require("./a.js"));\n' +
'\n' +
'var _b = _interopRequireDefault(require("./b.js"));\n' +
'\n' +
'function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n' +
'\n' +
'console.log(_a["default"].getB());\n' +
'console.log(_b["default"].getA());'
},
'a.js': {
deps: [ 'b.js' ],
code: '"use strict";\n' +
'\n' +
'Object.defineProperty(exports, "__esModule", {\n' +
' value: true\n' +
'});\n' +
'exports["default"] = void 0;\n' +
'\n' +
'var _b = _interopRequireDefault(require("./b.js"));\n' +
'\n' +
'function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n' +
'\n' +
'var a = {\n' +
" value: 'a',\n" +
' getB: function getB() {\n' +
` return _b["default"].value + ' from a.js';\n` +
' }\n' +
'};\n' +
'var _default = a;\n' +
'exports["default"] = _default;'
},
'b.js': {
deps: [ 'a.js' ],
code: '"use strict";\n' +
'\n' +
'Object.defineProperty(exports, "__esModule", {\n' +
' value: true\n' +
'});\n' +
'exports["default"] = void 0;\n' +
'\n' +
'var _a = _interopRequireDefault(require("./a.js"));\n' +
'\n' +
'function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }\n' +
'\n' +
'var b = {\n' +
" value: 'b',\n" +
' getA: function getA() {\n' +
` return _a["default"].value + ' from b.js';\n` +
' }\n' +
'};\n' +
'var _default = b;\n' +
'exports["default"] = _default;'
}
}

观察a.js变化

1
2
3
4
5
6
7
// a.js代码
import b from './b.js'
const a = {
value: 'a',
getB: () => b.value + ' from a.js'
}
export default a
  • import 不见了,变成了 require()
  • export 不见了,变成了 exports['default']
  • 这里的code 是字符串

es5后的 a.js内容字符串整理

  • 疑问1
    • 实际效果是 exports['__esModule']=true
    • 给当前模块添加 __esModule 属性,方便跟 CommonJS模块 区分
    • 这样写是为了解决特定环境的一些问题,不要细究。。。
  • 疑问2 exports["default"] = void 0;
    • void 0; // 等价于 赋值 undefined
    • 强制清空 exports['default']的值
  • 疑问3 import b from './b.js' 变成了 var _b = _interopRequireDefault(require("./b.js"));
    • _interopRequireDefault _ 代表内部的一个函数
    • 意图就是给模块添加 default
  • 疑问4
    • 给 require 的obj添加一个 default
    • 如果是 _esModule 代表有默认导出
    • 反之 obj = { "default": obj }
  • 疑问5 b.value 变成了 _b["default"].value
    • 因为 require 的obj 经过 _interopRequireDefault 处理,有了默认 obj:{default:obj}
    • 所以 变成了 _b["default"].value
  • 疑问6 var _default = a;exports["default"] = _default;
    • 简化后就是 exports['default'] = a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
"use strict"; //开启严格模式
// 疑问1
Object.defineProperty(exports, "__esModule", {value: true});
// 疑问2
exports["default"] = void 0;

// 疑问3
var _b = _interopRequireDefault(require("./b.js"));

// 疑问4
function _interopRequireDefault(obj) {
return obj && obj._esModule ? obj : { "default": obj };
}

var a = {
value: 'a',
getB: function getB() {
// 疑问5
return _b["default"].value + ' from a.js';
}
};

// 疑问6
var _default = a;
exports["default"] = _default;
  • 结论:我们通过 babel/core 把 ESModule 语法变成了 CommonJS 规则
    • CommonJS 就是使用 require/exports 定义模块

打包所有文件成为一个文件

  • 所有改动参考 bundler_1.ts => bundler_1.ts

第一步:改变depRelation的结构为数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var depRelation = [
{
key:'index.js',
deps:['a.js','b.js'],
code:`字符串code`
},
{
key:'a.js',
deps:['b.js'],
code:`字符串code`
},
{
key:'b.js',
deps:['a.js'],
code:`字符串code`
}
]

// 为什么把 depRelation 变成了数组,因为对象{} 没有第一项的概念
// 数组第一项是入口

第二步:code是字符串,需要变成一个函数

  • require, module, exports 是 CommonJS2 的规范
    • module基本用不到,可以忽略
    • 重点看 require/exports
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
code2 = `function (require, module, exports) {
${code}
}`
// 此时 code2还是字符串
// 把 `code:${code2}`写入到文件
code:function (require, module, exports) {
// ... code代码
}

// 最终写入的文件效果
var depRelation = [
{
key:'index.js',
deps:['a.js','b.js'],
code:function (require, module, exports) {
// ... code代码
}
},
...
]

第三步:execute 这个入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const modules = {} // 用于缓存,防止重复加载
execute(depRelation[0].key) // a.js
function execute(key) {
// 如果已经 require 过,就直接返回上次的结果
if (modules[key]) { return modules[key] }
// 找到 a.js 的代码
var item = depRelation.find(i => i.key === key)
// 找不到就报错,中断执行
if (!item) { throw new Error(`${item} is not found`) }
// 把相对路径变成项目路径
var pathToKey = (path) => {...}
// 创建 require 函数
var require = (path) => {
return execute(pathToKey(path))
}
// 初始化当前模块
// modules['a.js'] = { __esModule: true } 辨识我是ES模块
modules[key] = { __esModule: true }
// 初始化 module 方便 code 往 module.exports 上添加属性
// module = { exports: modules['a.js'] }
var module = { exports: modules[key] }
// 调用 code 函数,往 module.exports 上添加导出属性
// 第二个参数 module 大部分时候是无用的,主要用于兼容旧代码
item.code(require, module, module.exports)
// item.code执行的时候,把它导出的内容挂在 module.exports

// 返回当前模块 modules['a.js']
return modules[key]
}

最终dist.js文件的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
var depRelation = [{
key: "index.js",
deps: ["a.js", "b.js"],
code: function (require, module, exports) {
"use strict";

var _a = _interopRequireDefault(require("./a.js"));

var _b = _interopRequireDefault(require("./b.js"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

console.log(_a["default"].getB());
console.log(_b["default"].getA());
}
}, {
key: "a.js",
deps: ["b.js"],
code: function (require, module, exports) {
"use strict";

Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;

var _b = _interopRequireDefault(require("./b.js"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

var a = {
value: 'a',
getB: function getB() {
return _b["default"].value + ' from a.js';
}
};
var _default = a;
exports["default"] = _default;
}
}, {
key: "b.js",
deps: ["a.js"],
code: function (require, module, exports) {
"use strict";

Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;

var _a = _interopRequireDefault(require("./a.js"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

var b = {
value: 'b',
getA: function getA() {
return _a["default"].value + ' from b.js';
}
};
var _default = b;
exports["default"] = _default;
}
}]
var modules = {}
execute(depRelation[0].key)
function execute(key) {
// 如果已经 require 过,就直接返回上次的结果
if (modules[key]) { return modules[key] }
// 找到要执行的项目
var item = depRelation.find(i => i.key === key)
// 找不到就报错,中断执行
if (!item) { throw new Error(`${item} is not found`) }
// 把相对路径变成项目路径
var pathToKey = (path) => {
var dirname = key.substring(0, key.lastIndexOf('/') + 1)
var projectPath = (dirname + path).replace(/\.\//g, '').replace(/\/\//, '/')
return projectPath
}
// 创建 require 函数
var require = (path) => {
return execute(pathToKey(path))
}
// 初始化当前模块
modules[key] = { __esModule: true }
// 初始化 module 方便 code 往 module.exports 上添加属性
var module = { exports: modules[key] }
// 调用 code 函数,往 module.exports 上添加导出属性
// 第二个参数 module 大部分时候是无用的,主要用于兼容旧代码
item.code(require, module, module.exports)
// 返回当前模块
return modules[key]
}

打包器完成

  • 拼接构造 dist.js 的内容,然后写入到文件
  • 参考 bundeler_3.ts 内容

Webpack-01AST_Babel_依赖

Webpack

前置工作

  • 下载代码仓库
  • 安装好依赖,参考README 运行我们的代码

Babel原理

  • parse:把代码code变成 AST
  • traverse:遍历 AST 进行修改
  • generate:把修改后的 AST 变成 code2

过程如下

1
2
       parse        traverse       generate
code --------> ast -------> ast2 -------> code2

为啥不用 “正则” 来实现

  • 因为代码很复杂 let a = 'let' 单纯一个变量声明,你就很难处理,更何况其他ES6及以上语法呢?

let to var

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { parse } from "@babel/parser"
import traverse from "@babel/traverse"
import generate from "@babel/generator"

const code = `let a = 'let'; let b = 2`
const ast = parse(code, { sourceType: 'module' })
traverse(ast, {
enter: item => {
if (item.node.type === 'VariableDeclaration') {
if (item.node.kind === 'let') {
item.node.kind = 'var'
}
}
}
})
const result = generate(ast, {}, code)
console.log(result.code)

代码转换为ES5

1
2
3
4
5
6
7
8
9
import { parse } from "@babel/parser"
import * as babel from "@babel/core"

const code = `let a = 'let';let b = 2; const c = 3`
const ast = parse(code, { sourceType: 'module' })
const result = babel.transformFromAstSync(ast, code, {
presets: ['@babel/preset-env']
})
console.log(result.code)

将js文件转换为ES5

1
2
3
4
5
6
7
8
9
10
import { parse } from "@babel/parser"
import * as babel from "@babel/core"
import * as fs from 'fs'

const code = fs.readFileSync('./test.js').toString()
const ast = parse(code, { sourceType: 'module' })
const result = babel.transformFromAstSync(ast, code, {
presets: ['@babel/preset-env']
})
fs.writeFileSync('./test.es5.js', result.code)

除了转换ES5代码还能做什么

依赖分析: 004_dep.ts

  • project1目录的 index.js
1
2
3
import a from './a.js'
import b from './b.js'
console.log(a.value + b.value)

代码思路 004_dep.ts

1、调用 collectCodeAndDeps(‘index.js’)
2、 先把 depRelation[‘index.js’] 初始化为 { deps: [], code: ‘index.js的源码’ }
3、然后把 index.js 源码 code 变成 ast
4、遍历 ast,看看 import 了哪些依赖,假设依赖了 a.js 和 b.js
5、把 a.js 和 b.js 写到 depRelation[‘index.js’].deps 里
6、最终得到的 depRelation 就收集了 index.js 的依赖

升级:依赖里还有依赖 005_dep.ts

  • 三层依赖关系

  • index.js -> a -> dir/a2 -> dir/dir_in_dir/a3

  • index.js -> b -> dir/b2 -> dir/dir_in_dir/b3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function collectCodeAndDeps(filepath: string) {
const key = getProjectPath(filepath) // 文件的项目路径,如 index.js
// 获取文件内容,将内容放至 depRelation
const code = readFileSync(filepath).toString()
// 初始化 depRelation[key]
depRelation[key] = { deps: [], code: code }
// 将代码转为 AST
const ast = parse(code, { sourceType: 'module' })
// 分析文件依赖,将内容放至 depRelation
traverse(ast, {
enter: path => {
if (path.node.type === 'ImportDeclaration') {
// path.node.source.value 往往是一个相对路径,如 ./a.js,需要先把它转为一个绝对路径
const depAbsolutePath = resolve(dirname(filepath), path.node.source.value)
// 然后转为项目路径
const depProjectPath = getProjectPath(depAbsolutePath)
// 把依赖写进 depRelation
depRelation[key].deps.push(depProjectPath)

// 递归依赖收集的核心一句话
// 递归依赖收集的核心一句话
// 递归依赖收集的核心一句话
collectCodeAndDeps(depAbsolutePath)
}
}
})
}

思路

  • collectCodeAndDeps 太长了,缩写为 collect
  • 调用 collect(‘index.js’)
  • 发现依赖 ‘./a.js’ 于是调用 collect(‘a.js’)
  • 发现依赖 ‘./dir/a2.js’ 于是调用 collect(‘dir/a2.js’)
  • 发现依赖 ‘./dir_in_dir/a3.js’ 于是调用 collect(‘dir/dir_in_dir/a3.js’)
  • 没有更多依赖了,a.js 这条线结束,发现下一个依赖 ‘./b.js’
  • 以此类推,其实就是递归

代码如果是循环依赖呢? 006_dep.ts

  • index -> a -> b
  • index -> b -> a
1
2
3
4
5
6
7
8
9
10
11
12
13
// a.js
import b from './b.js'
const a = {
value: b.value + 1,
}
export default a

// b.js
import a from './a.js'
const b = {
value: a.value + 1,
}
export default b
  • 求值
1
2
3
4
5
6
7
8
9
// a.js里
a.value = b.value +1
// b.js里
b.value = a.value +1

// 神经病,a 依赖b,但是 b必须先执行,同时 b又依赖 a
// 运行代码 报错
Maximum call stack size exceeded
// 爆栈了

难道不能 “循环依赖”吗?007_dep.ts

解决循环依赖问题,用一个map ,如果已经读取过,就return

1
2
3
4
if (Object.keys(depRelation).includes(key)) {
console.warn(`duplicated dependency: ${key}`) // 注意,重复依赖不一定是循环依赖
return
}
  • 分析依赖,是不需要执行代码的,所以这是可行的
  • 由于不需要执行代码,所以这个叫做“静态分析”
  • 假如执行代码,就会出现循环引用问题

结论

  • 模块里可以循环依赖
    • a->b,b->a
  • 但是不能有逻辑漏洞
    • a.value = b.value +1
    • b.value = a.value +1
  • 如何写出解决循环依赖的代码 project5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// a.js
import b from './b.js'
const a = {
value: 'a',
getB: () => b.value + ' from a.js'
}
export default a

// b.js
import a from './a.js'
const b = {
value: 'b',
getA: () => a.value + ' from b.js'
}
export default b

这样循环链就断了,运行就不会报错

总结

AST

  • parse 将 code 变成 ast
  • traverse: 遍历 AST 进行修改
  • generate: 把修改后的 AST 变成代码 code2

工具

  • babel 可以把高级代码翻译为 ES5
  • @babel/parser
  • @babel/traverse
  • @babel/generator
  • @babel/core 包含前三者
  • @babel/preset-env 内置很多规则

循环依赖

  • 有的循环依赖可以正常执行
  • 有的循环依赖不可以
  • 但都可以做静态分析

Webpack-00前置内容理解webpack

理解 Webpack 实现流程

理解node 模块化

src目录有 如下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
//name.js
let name = 'my name is xxx'
exports.name = name

// action.js
let action = 'make webpack'
exports.action = action

// index.js
let action = require('./action.js').action
let name = require('./name.js').name
let msg = `${name} is ${action}`
console.log(msg)
  • 如果你学过模块化,or用过 require.js
  • 意思是每个文件代表一个模块,当一个文件(index.js)需要另一个文件内容的时候(name.js) ,我们可以 require('./name.js')
  • require得到的东西,就是这个模块文件如name.js 里 exports对象
  • 可以通过 require 拿到一个模块里的东西,同时可以用 exports 把模块需要暴露出的东西 暴露出去
  • require/exports 就实现了模块化

    • 好处是:代码更加清晰,修改模块的时候更加的方便
  • 运行 node src/index.js 得到执行

怎么就能执行了?

  • 因为我们使用的是 node环境,而node环境内部天生支持模块化,使用的是 commonJS规范

如何支持浏览器?

  • 不可以直接引入 index.js ,因为浏览器没有 require/exports 这些东西
  • 你需要对源码进行处理
  • 使用“一些工具”对代码进行处理加工变成一个文件,放到dist里 浏览器引入 dist里的js
  • 这个工具就是 webpack

实现思路

  • 浏览器是不认识 require/exports 的

step01先套壳(此时代码是无法运行的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// index.js
function (require, exports, module) {
let action = require('./action.js').action
let name = require('./name.js').name
let msg = `${name} is ${action}`
console.log(msg)
}

// action.js
function (require, exports, module) {
let action = 'make webpack'
exports.action = action
}

// name.js
function (require, exports, module) {
let name = 'my name is xxx'
exports.name = name
}

step02 模块有一个模块id,放入到一个对象里modules(命名空间)

  • 如下代码还无法运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
modules = {
// index.js
0: function (require, exports) {
let action = require('./action.js').action
let name = require('./name.js').name
let msg = `${name} is ${action}`
console.log(msg)
},
// action.js
1: function (require, exports) {
let action = 'make webpack'
exports.action = action
},
// name.js
2: function (require, exports) {
let name = 'my name is xxx'
exports.name = name
}
}

// 所有的模块归拢到一处
// 用一个 modules 收集
// 过程就是,我们又一个工具 比如叫做 webpack,把我们的代码变成如下内容,然后把每个modules挨个执行一下

// 执行模块返回结果
function exec(id) {
let fn = modules[id]
let exports = {}
fn(require, exports)

// 浏览器不是 node环境,所以没有 require,我们需要创造一个require
function require(path) {
// 根据模块的路径,返回执行的结果
}
}

// 模块0 是我们的入口文件
exec(0)
  • 浏览器不是node环境没有require
  • 浏览器不是node环境没有require
  • 浏览器不是node环境没有require
  • 所以webpack的作用是把模块化的js处理成一个文件如 bundle.js 在浏览器执行

COMMONJS规范写源码的时候

  • 必须用 require(‘xx.js’) 去加载一个模块
  • 就是去执行这个模块,拿到这个模块里的 exports 对象
  • 同时模块希望被别人用的时候,我们需要在 exports 绑定东西

step03改变modules的数据格式

  • 资源映射和启动入口
  • 代码可以在 “node环境/浏览器”执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
modules = {
// index.js
0: [
function (require, exports) {
let action = require('./action.js').action
let name = require('./name.js').name
let msg = `${name} is ${action}`
console.log(msg)
},
// 依赖的模块 路径和 id的映射
{
'./action.js': 1,
'./name.js': 2
}
],
// action.js
1: [
function (require, exports) {
let action = 'make webpack'
exports.action = action
},
{}
],
// name.js
2: [
function (require, exports) {
let name = 'my name is xxx'
exports.name = name
},
{}
]
}

// 所有的模块归拢到一处
// 用一个 modules 收集
// 过程就是,我们又一个工具 比如叫做 webpack,把我们的代码变成如下内容,然后把每个modules挨个执行一下

// 执行模块返回结果
function exec(id) {
/*
mapping 形如
{
'./action.js': 1,
'./name.js': 2
}
*/
let [fn, mapping] = modules[id]
let exports = {}
fn && fn(require, exports)

// 浏览器不是 node环境,所以没有 require,我们需要创造一个require
function require(path) {
// 根据模块的路径,返回执行的结果
return exec(mapping[path])
}
return exports
}

// 模块0 是我们的入口文件
exec(0)

Node-web05-09-03博客系统使用nginx

基于上一篇你构建好的情况下,不然不用看了

Nginx咋用

1
2
3
4
5
6
7
# 看见一个模版命令
docker run --name some-nginx -v /some/content:/usr/share/nginx/html:ro -d nginx

# --name给容器起个名字
# -v 挂载的内容
# -d 持久的运行
# --network=host 容器共享阿里云的端口

CRM之 改改试一下

1
2
3
4
5
ssh blog@dev1
docker run --name nginx1 --network=host -d nginx:1.19.1

# 去安全组开放 80端口
浏览器访问你的 ip 即可

nginx配置

  • 最全版参考

  • ~/nginx.conf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    server{
    # ip v4
    listen 80;
    listen [::]:80;
    server_name localhost;

    # 所有的流量导入如下地方
    location / {
    # 反向代理,流量导入这里
    proxy_pass http://0.0.0.0:3000;
    }
    }

再次启动 nginx

1
2
3
4
5
docker run --name nginx1 --network=host -v ~/nginx.conf:/etc/nginx/conf.d/default.conf -d nginx:1.19.1

此时浏览器访问 你的 ip 即可
# 安全组关闭 3000端口
# 开放 80端口

让next.js的静态文件 不走next.js 而是通过 nginx转发

  • 比如一个资源http://8.130.54.208/_next/static/css/5e2b15b35c6f85e72313.css
    • 流量走向 next.js的进程
  • 我们希望通过 nginx 把静态文件资源转发到 静态服务器
  • 修改配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server{
# ip v4
listen 80;
listen [::]:80;
server_name localhost;

# 将静态文件转发到静态服务器
location ~ ^/_next/static/ {
root /usr/share/nginx/html/;
expires 30d;
}

# 所有的流量导入如下地方
location / {
# 反向代理,流量导入这里
proxy_pass http://0.0.0.0:3000;
}
}
  • 修改启动nginx 命令
1
2
# 静态文件也需要挂载目录
docker run --name nginx1 --network=host -v ~/nginx.conf:/etc/nginx/conf.d/default.conf -v ~/app/.next/static/:/usr/share/nginx/html/_next/static -d nginx:1.19.1

开启 gzip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
server{
# ip v4
listen 80;
listen [::]:80;
server_name localhost;
gzip on;
gzip_disable "msie6";

gzip_comp_level 6;
gzip_min_length 1100;
gzip_buffers 16 8k;
gzip_proxied any;
gzip_types
text/plain
text/css
text/js
text/xml
text/javascript
application/javascript
application/x-javascript
application/json
application/xml
application/rss+xml
image/svg+xml/javascript;

# 将静态文件转发到静态服务器
location ~ ^/_next/static/ {
root /usr/share/nginx/html/;
expires 30d;
}

# 所有的流量导入如下地方
location / {
# 反向代理,流量导入这里
proxy_pass http://0.0.0.0:3000;
}
}
  • 再次运行容器
1
docker run --name nginx1 --network=host -v ~/nginx.conf:/etc/nginx/conf.d/default.conf -v ~/app/.next/static/:/usr/share/nginx/html/_next/static -d nginx:1.19.1

总结

nginx的好处

静态文静访问更快

  • 动静分离
  • 静态资源可以直接到文件系统,好处是 nginx访问静态文件比 nodejs访问快
  • node需要读文件流
  • nginx专业做这个的
  • 还可以添加 gzip

反向代理

  • 你可以在不同机器上有多个node程序,通过 nginx 转发
    • 当你业务大了,可以水平扩展
  • 负载均衡

Node-web05-09-02博客系统部署最新版流程

前置条件

  • 充值110元 购买阿里云按量付费机器
  • 安装好 docker
  • 安装好 node
  • 配置好阿里云服务器的 root以及非root用户的 ssh 登陆
  • 配置好 非root用户 如 blog 用户的 docker执行权限
  • 配置好 git ssh key 用来clone / pull代码

登录服务器

1
ssh blog@dev1

设置环境变量

1
2
3
4
5
6
7
8
vi ~/.bashrc
# 插入一句,后保存
export NODE_ENV=production

# 让这个变动生效
source ~/.bashrc
# 验证
echo $NODE_ENV

PG的一些设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 创建数据库持久化目录
mkdir ~/blog-data

# 这里注意这个 --name my-pg-app 后面会用到,非常重要
docker run --name my-pg-app -v ~/blog-data:/var/lib/postgresql/data --network=host -e POSTGRES_USER=blog -e POSTGRES_HOST_AUTH_METHOD=trust -d postgres:12.2

# 通过id or appname(--name 指定的名字)进入
# docker exec -it <id|appname> bash
# 创建数据库,这里通过这个 [my-pg-app] 进入数据库内部 ,你也可通过 容器id
# 通过应用名进入
docker exec -it my-pg-app bash

# 登录数据库
psql -U blog

# 如果是服务器 生产环境你就这样
CREATE DATABASE blog_production ENCODING 'UTF8' LC_COLLATE 'en_US.utf8' LC_CTYPE 'en_US.utf8';

# 如果是本地你就这样
CREATE DATABASE blog_development ENCODING 'UTF8' LC_COLLATE 'en_US.utf8' LC_CTYPE 'en_US.utf8';

拉代码

1
2
3
4
5
6
cd ~/
mkdir app
cd app

# 拉代码,前提你配置好 github的ssh key
git clone git@github.com:slTrust/next-demo-2.git .

数据库配置

  • 参考 ~/app/ormconfig.sample.json 建立 production 环境的 config

  • 在服务器 ~/shared/ormconfig.json这样建立文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
cd ~/
mkdir shared
vi ormconfig.json

# 内容如下,根据你的 pg设置自行设置
{
"type": "postgres",
"host": "localhost",
"port": 5432,
"username": "blog",
"password": "",
"database": "blog_production",
"synchronize": false,
"logging": false,
"entities": [
"dist/entity/**/*.js"
],
"migrations": [
"dist/migration/**/*.js"
],
"subscribers": [
"dist/subscriber/**/*.js"
],
"cli": {
"entitiesDir": "src/entity",
"migrationsDir": "src/migration",
"subscribersDir": "src/subscriber"
}
}
  • 建立软链接(非常重要)
1
2
3
# 注意要在项目根目录
cd ~/app
ln -s ~/shared/ormconfig.json

加密密钥配置

1
2
3
4
5
cd ~/app
touch .env.local

# 内容如下
SECRET=c2a85490-cc60-4f21-94e8-8dc5dd3220da

deploy.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
echo '前置准备完成了,启动pg';
docker restart my-pg-app &&
# 推荐这样,因为用户名不一定都叫 blog
cd ~/app/ &&
git pull &&
yarn install --production=false &&
yarn build &&
# 修改补丁, src/entity/User.ts 的补丁
git apply migrate.patch;
# 编译src/migration ,让typeorm 的ts变成 js
yarn compile &&
# 数据库迁移,创建表
yarn m:run &&
# 撤销 patch内容
git reset --hard HEAD &&
# 防止同名应用存在
docker kill my-next-app;
docker rm -f my-next-app;
docker rmi -f hjx/node-web-app;
# docker构建应用
docker build -t hjx/node-web-app . &&
docker run --name my-next-app --network=host -d hjx/node-web-app &&
echo 'OK!'

docker -p 参数端口映射一个重要知识点

  • docker 容器内的localhost 不是你机器的localhost

docker --network=host

  • 意思是 你服务器的 localhost:端口 对应你的容器开放的端口
1
2
3
4
5
# 当访问你机器的 3000 时映射到对应容器的 3000端口
docker run --name my-next-app -p 3000:3000 -d hjx/node-web-app

# 不再指定 -p 参数
docker run --name my-next-app --network=host -d hjx/node-web-app

一键部署

1
2
3
4
5
6
7
8
# 前置准备工作
ssh blog@dev1
# clone 我的最新代码
# 分配执行权限
chmod +x ~/app/bin/deploy.sh

# 以后每次改了代码 push之后你就可以这样
ssh blog@dev1 'sh ~/app/bin/deploy.sh'

Node-web05-09-01博客统部署优化解释

上次部署的服务器

我们在服务器修改了部分文件,如果直接 git pull 就会有问题

git 通灵术使用

  • git status 查看修改的文件状态
  • 假如我们服务器的代码 修改了 xx.txt 内容,拉取的时候不想冲突
  • 你就 git stash 把它存入一个地方
  • git status 状态就是空的了
  • 然后 git pull 下载最新代码
  • 恢复刚才藏起来的代码 git stash pop

设置环境变量导致的bug

  • 我们设置了 export NODE_ENV=production
  • 这样 yarn install 的时候 dev依赖就会被忽略
  • 解决办法是
1
yarn install --production=false

在服务器执行本地脚本

1
2
3
4
5
6
7
ssh blog@dev1 "echo hi > /tmp/xxx"

# 你会发现你服务器上的 多了`/tmp/xxx` 文件 内容是 hi

# 项目里声明一个 deploy.sh
# 本地的脚本在服务器端执行
ssh blog@dev1 "bash -s < bin/deploy.sh"

解决json配置两个环境不一致问题

  • 由于json不能改,所以这是一个痛点,因为本地和 生产环境 的配置“不一致”
  • 复制 ormconfig.json 内容到ormconfig.sample.json
  • 然后 .gitignore里 忽略ormconfig.json

解决配置文件要在git pull 之后修改的问题

  • 在服务器 ~/shared/ormconfig.json这样建立文件
  • 然后在你的项目里 通过“软链接” 来分离配置和项目代码
  • 项目目录里运行ln -s ~/shared/ormconfig.json
    • 这样你服务器环境的配置文件是 软链接的文件
    • 你本地开发可以自己参考 ormconfig.sample.json 建立dev的配置ormconfig.json
    • 这样两边就不一致了

补丁包使用

  • 我们的 src/entity/User.ts 里有数据库validate操作 yarn m:run 数据库迁移时会报错
  • 所以服务器和本地的时候,我们都要把对应操作注释掉执行 yarn m:run迁移数据库,然后在把注释恢复
  • 这样就非常的 ”蛋疼“

git create patch from diff

  • step01 假如数据库迁移的时候需要的xxx.js文件内容是
1
2
3
aaa
// bbb
ccc
  • step02 而数据库迁移之后,我们xxx.js 的内容是这样
1
2
3
aaa
bbb
ccc
  • step03 将git diff的内容 完整拷贝到 migrate.patch 文件里
1
git diff > migrate.patch
  • step04 然后取消掉 migration 要做的文件改动,即 xxx.js内容为
1
2
3
aaa
bbb
ccc
  • step05 运行补丁,
1
2
3
4
5
6
git apply migrate.patch

# 运行后 xxx.js 内容为
aaa
//bbb
ccc
  • step06 撤销操作git reset --hard HEAD

Node-web05-07-03服务器端构建

克隆代码

1
2
3
4
5
6
7
8
9
10
11
12
# blog用户登录
ssh blog@dev1

# 进入服务器home目录
cd ~/
# 再次 clone代码
git clone git@github.com:slTrust/next-demo-2.git
# 进入项目根目录
cd next-demo-2

# 切换到我那个时候的代码,以保证同步
git checkout bbc97ab4fc6adf3b93b136bcfeb0ad409c9711e4

项目开始运行

  • 切换用户su - blog 到你的项目目录 cd ~/app/next-demo-2
  • 运行 yarn install
  • 然后 yarn build 结果报错了, 说缺少一个 password ,记得我们忽略的 .env.local
  • 顺带把 数据库连接信息改了
1
2
3
4
5
6
7
8
9
10
11
12
13
# 项目根目录 
touch .env.local
# 内容如下,注意密钥要用你的
SECRET=c2a85490-cc60-4f21-94e8-8dc5dd3220da

# 然后修改 pg数据库的配置 vi ormconfig.json
把host 改成你机器的 pg数据库信息

"host": "172.17.48.128", // 你机器的ip ifconfig查看
"port": 5432, // docker 启动pg设置的端口
"username": "blog", // pg用户名
"password": "", // pg密码
"database": "blog_production"

这也是为啥配置别用json的原因,没法改
这也是为啥配置别用json的原因,没法改
这也是为啥配置别用json的原因,没法改

  • 运行yarn dev 然后 curl -L localhost:3000
  • 提示你 QueryFailedError: relation "posts" does not exist

创建数据库表

  • yarn typeorm:build
  • yarn m:run 报错 Error: Cannot find module '../../lib/getDatabaseConnection'
  • 因为 src/entity/User.ts 里用了 getDatabaseConnection 要把对应的语句注释
1
2
3
4
5
6
7
8
9
// 13行的
// import {getDatabaseConnection} from '../../lib/getDatabaseConnection';

// 55~59
//const found = await (await getDatabaseConnection()).manager.find(
User, {username: this.username});
// if (found.length > 0) {
// this.errors.username.push('已存在,不能重复注册');
// }
  • 继续 yarn typeorm:build然后yarn m:run
  • 此时:数据库表结构创建好了,把 src/entity/User.ts还原
  • 运行 yarn build
    • 然后 yarn start
    • curl -L localhost:3000 此时页面有了但是没有数据

不能在docker里 yarn build 里面会有很多文件,很大

docker 你的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 进入你的项目目录
cd ~/app/next-demo-2/

# 构建应用
docker build -t my-node-app/node-web-app .

docker run -p 3000:3000 -d my-node-app/node-web-app

# 访问我们的应用,里面报错,但是可以先不管
curl -L localhost:3000

# 查看占用端口
lsof -i :3000
# 杀掉进程
kill pid

查看历史命令小技巧

1
history | grep 'docker run'

解决报错

1
2
3
4
5
6
7
8
9
10
# 查看我们应用的id
docker ps -a
# 看log
docker logs id

# log内容
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
error: database "blog_production" does not exist

# 数据库不存在,因为我们没有初始化数据库

设置环境变量,服务器是 production

1
2
3
4
5
6
7
8
9
10
11
# root用户 登服务器
ssh root@dev1
# 设置环境变量
vi ~/.bashrc
export NODE_ENV=production

# 刷新这个文件
source ~/.bashrc

# 测试是否生效
echo $NODE_ENV

NODE_ENV=production 后,yarn install 不会install dev的东西

  • 你要这样 yarn install --production=false

docker的--network=host选项

  • 让容器共享你 阿里云机器的IP,这样就可以不做端口映射
  • 数据库连接不上问题
    • 容器的localhost 不是你本机的 localhost
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      # 把之前的容器都停了,然后删了  
      docker kill id
      docker rm -f id

      # docker重新启动 pg
      docker run --network=host -v /home/blog/blog-data:/var/lib/postgresql/data -p 5432:5432 -e POSTGRES_USER=blog -e POSTGRES_HOST_AUTH_METHOD=trust -d postgres:12.2

      # 重新构建next.js应用
      docker build -t my-node-app/node-web-app .

      docker run --network=host -p 3000:3000 -d my-node-app/node-web-app