├── .gitignore
├── LICENSE
├── README.md
├── docs
├── .vuepress
│ ├── config.js
│ ├── enhanceApp.js
│ └── public
│ │ ├── css
│ │ └── index.css
│ │ └── images
│ │ ├── babel
│ │ ├── 0001.png
│ │ ├── 0002.png
│ │ ├── 0003.png
│ │ ├── 0004.png
│ │ ├── 0005.png
│ │ ├── 0006.png
│ │ ├── 0007.png
│ │ ├── 0008.png
│ │ ├── 0009.png
│ │ ├── 0010.png
│ │ ├── 0011.png
│ │ ├── 0012.png
│ │ ├── 0013.png
│ │ ├── 0014.png
│ │ ├── 0015.png
│ │ ├── 0016.png
│ │ ├── 0017.png
│ │ ├── 0018.png
│ │ ├── 0019.png
│ │ ├── 0020.png
│ │ ├── 0021.png
│ │ ├── 0022.jpeg
│ │ ├── 0023.jpeg
│ │ ├── 0024.jpeg
│ │ ├── 0025.jpeg
│ │ ├── 0026.jpeg
│ │ ├── 0027.jpeg
│ │ ├── 0028.jpeg
│ │ └── 0029.jpeg
│ │ ├── browser
│ │ ├── 0001.jpeg
│ │ ├── 0002.png
│ │ ├── 0003.png
│ │ ├── 0004.png
│ │ ├── 0005.jpeg
│ │ ├── 0006.jpeg
│ │ ├── 0007.jpeg
│ │ ├── 0008.png
│ │ ├── 0009.png
│ │ ├── 0010.png
│ │ ├── 0011.png
│ │ ├── 0012.png
│ │ ├── 0013.png
│ │ ├── 0014.png
│ │ ├── 0015.png
│ │ ├── 0016.png
│ │ ├── 0017.png
│ │ ├── 0018.png
│ │ ├── 0019.png
│ │ ├── 0020.png
│ │ ├── 0021.png
│ │ ├── 0022.png
│ │ ├── 0023.png
│ │ ├── 0024.png
│ │ └── 0025.png
│ │ ├── data-structure
│ │ ├── 0001.png
│ │ ├── 0002.gif
│ │ ├── 0003.gif
│ │ ├── 0004.gif
│ │ ├── 0005.gif
│ │ └── 0006.gif
│ │ ├── interview-questions
│ │ ├── 0001.png
│ │ └── 0002.png
│ │ ├── notes.png
│ │ ├── notes
│ │ ├── 0001.jpeg
│ │ ├── 0002.png
│ │ └── 0003.png
│ │ ├── performance-optimization
│ │ └── 0001.png
│ │ ├── redux-flow.png
│ │ └── vue
│ │ ├── 0001.jpeg
│ │ └── 0002.png
├── README.md
├── babel
│ ├── 001_babel_introduce.md
│ ├── 002_babel_ast.md
│ ├── 003_babel_api.md
│ ├── 004_demo_insert_params.md
│ ├── 005_history_of_js_parser.md
│ ├── 006_traverse_path_scope_visitor.md
│ ├── 007_generator_sourcemap.md
│ ├── 008_babel_plugin_preset.md
│ ├── README.md
│ └── babel7.md
├── browser
│ ├── README.md
│ ├── browser-cache.md
│ ├── browser-chrome.md
│ ├── browser-security.md
│ ├── client-offset-scroll.md
│ ├── input-url-to-page.md
│ ├── ip-udp-tcp.md
│ └── virtual-dom.md
├── contact
│ └── index.md
├── css
│ └── weather.md
├── data-structure
│ └── based-algorithm.md
├── interview-questions
│ ├── README.md
│ ├── css
│ │ ├── CSS.xmind
│ │ └── index.md
│ └── html
│ │ ├── HTML.xmind
│ │ └── index.md
├── mac-os
│ ├── brew.md
│ └── tree.md
├── notes
│ ├── css-units.md
│ ├── debounceAndThrottle.md
│ ├── dependencies.md
│ ├── design-patterns.md
│ ├── jsModular.md
│ ├── operationalMechanism.md
│ ├── package-json.md
│ ├── postMessage.md
│ └── vue-create-api.md
├── pages
│ ├── ast
│ │ ├── README.md
│ │ └── node-introduced.md
│ └── performance-optimization
│ │ ├── README.md
│ │ ├── cdn.md
│ │ ├── image-optimization.md
│ │ ├── load-on-demand.md
│ │ ├── reflow-and-repaint.md
│ │ ├── throttle-and-debounce.md
│ │ └── webpack-optimization.md
├── question
│ └── index.md
├── react
│ ├── README.md
│ ├── base-hooks.md
│ └── redux.md
├── server
│ └── next
│ │ ├── middleware.md
│ │ ├── module.md
│ │ └── providers.md
├── tools
│ ├── city.md
│ ├── drawer.md
│ └── tabBar.md
├── vue
│ ├── README.md
│ ├── implementation-principle.md
│ └── vuex.md
├── webpack
│ ├── README.md
│ ├── custom-loader.md
│ ├── custom-plugin.md
│ ├── dll-plugin.md
│ └── webpack-node-externals.md
└── zh
│ └── guide
│ └── README.md
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | docs/.vuepress/dist/
3 | package-lock.json
4 | yarn.lock
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 dengwb
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 基于 vuepress 搭建的个人技术文章博客
5 |
6 | ## 链接目录
7 |
8 | > Css
9 |
10 | [天气](https://notes.dengwb.com/css/weather.html)
11 |
12 | > 源码解析
13 |
14 | [vue-create-api](https://notes.dengwb.com/notes/vue-create-api.html)
15 |
16 | > 其他
17 |
18 | [解决跨域传值方案](https://notes.dengwb.com/notes/postMessage.html)
19 |
20 | [函数防抖节流](https://notes.dengwb.com/notes/debounceAndThrottle.html)
21 |
22 | [JS模块化](https://notes.dengwb.com/notes/jsModular.html)
23 |
24 | [JS运行机制](https://notes.dengwb.com/notes/operationalMechanism.html)
25 |
--------------------------------------------------------------------------------
/docs/.vuepress/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | port: 8083,
3 | title: 'Dengwb 随行笔记',
4 | description: '小站',
5 | head: [
6 | ['link', { rel: 'icon', href: '/images/notes.png' }],
7 | ['link', { rel: 'stylesheet', href: '/css/index.css' }],
8 | ],
9 | themeConfig: {
10 | // The navigation bar
11 | nav: [
12 | { text: 'Home', link: '/' },
13 | {
14 | text: 'Reading',
15 | items: [
16 | { text: 'vue-create-api源码解析', link: '/notes/vue-create-api' },
17 | { text: '跨域传值', link: '/notes/postMessage' },
18 | { text: '函数防抖节流', link: '/notes/debounceAndThrottle' },
19 | { text: 'Javascript模块化', link: '/notes/jsModular' },
20 | { text: 'Javascript运行机制', link: '/notes/operationalMechanism' },
21 | { text: 'css单位', link: '/notes/css-units' },
22 | { text: '设计模式', link: '/notes/design-patterns' },
23 | { text: 'package.json冷门属性配置', link: '/notes/package-json' },
24 | { text: 'dev/peer/dependencies', link: '/notes/dependencies' }
25 | ]
26 | },
27 | {
28 | text: 'Css',
29 | items: [
30 | { text: 'Weather', link: '/css/weather' }
31 | ]
32 | },
33 | {
34 | text: 'React',
35 | items: [
36 | { text: 'Redux', link: '/react/redux' },
37 | { text: '基础Hooks', link: '/react/base-hooks' }
38 | ]
39 | },
40 | {
41 | text: 'Vue',
42 | items: [
43 | { text: '实现原理', link: '/vue/implementation-principle' },
44 | { text: 'vuex', link: '/vue/vuex' }
45 | ]
46 | },
47 | {
48 | text: 'Webpack',
49 | items: [
50 | { text: 'DllPlugin', link: '/webpack/dll-plugin' },
51 | { text: 'NodeExternals', link: '/webpack/webpack-node-externals' }
52 | ]
53 | },
54 | {
55 | text: '浏览器',
56 | items: [
57 | { text: 'Chrome架构', link: '/browser/browser-chrome' },
58 | { text: 'IP & UDP & TCP', link: '/browser/ip-udp-tcp' },
59 | { text: '从输入URL到页面展示', link: '/browser/input-url-to-page' },
60 | { text: '浏览器缓存机制', link: '/browser/browser-cache' },
61 | { text: '浏览器安全', link: '/browser/browser-security' },
62 | { text: 'client & offset & scroll', link: '/browser/client-offset-scroll' }
63 | ]
64 | },
65 | {
66 | text: '数据结构',
67 | items: [
68 | { text: '基础算法', link: '/data-structure/based-algorithm' }
69 | ]
70 | },
71 | {
72 | text: 'MacOS',
73 | items: [
74 | { text: 'brew', link: '/mac-os/brew' },
75 | { text: 'tree', link: '/mac-os/tree' },
76 | ]
77 | },
78 | { text: 'Question', link: '/question/index' },
79 | { text: 'GitHub', link: 'https://github.com/dengwb1991/vue-press-notes' }
80 | ],
81 | // The sidebar
82 | sidebar: 'auto',
83 | lastUpdated: 'Last Updated'
84 | },
85 | plugins: [
86 | ['demo-code', {
87 | onlineBtns: {
88 | codepen: true,
89 | jsfiddle: false,
90 | codesandbox: false
91 | }
92 | }]
93 | ]
94 | }
--------------------------------------------------------------------------------
/docs/.vuepress/enhanceApp.js:
--------------------------------------------------------------------------------
1 | export default ({
2 | Vue, // VuePress 正在使用的 Vue 构造函数
3 | options, // 附加到根实例的一些选项
4 | router, // 当前应用的路由实例
5 | siteData // 站点元数据s
6 | }) => {
7 | }
--------------------------------------------------------------------------------
/docs/.vuepress/public/css/index.css:
--------------------------------------------------------------------------------
1 | /* ` `中的颜色 */
2 | .theme-default-content code {
3 | color: #ff502c!important;
4 | background-color: #fff5f5!important;
5 | }
6 |
7 | strong {
8 | border: 1px solid #CCC;
9 | background-color: #CCC;
10 | }
11 |
12 | .theme-default-content pre code, .theme-default-content pre[class*="language-"] code {
13 | background-color: transparent!important;
14 | }
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0001.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0001.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0002.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0002.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0003.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0003.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0004.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0004.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0005.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0005.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0006.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0006.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0007.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0007.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0008.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0008.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0009.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0009.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0010.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0010.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0011.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0011.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0012.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0012.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0013.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0013.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0014.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0014.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0015.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0015.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0016.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0016.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0017.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0017.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0018.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0018.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0019.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0019.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0020.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0020.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0021.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0021.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0022.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0022.jpeg
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0023.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0023.jpeg
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0024.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0024.jpeg
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0025.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0025.jpeg
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0026.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0026.jpeg
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0027.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0027.jpeg
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0028.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0028.jpeg
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/babel/0029.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/babel/0029.jpeg
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0001.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0001.jpeg
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0002.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0002.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0003.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0003.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0004.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0004.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0005.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0005.jpeg
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0006.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0006.jpeg
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0007.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0007.jpeg
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0008.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0008.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0009.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0009.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0010.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0010.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0011.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0011.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0012.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0012.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0013.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0013.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0014.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0014.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0015.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0015.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0016.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0016.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0017.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0017.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0018.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0018.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0019.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0019.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0020.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0020.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0021.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0021.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0022.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0022.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0023.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0023.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0024.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/browser/0025.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/browser/0025.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/data-structure/0001.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/data-structure/0001.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/data-structure/0002.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/data-structure/0002.gif
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/data-structure/0003.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/data-structure/0003.gif
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/data-structure/0004.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/data-structure/0004.gif
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/data-structure/0005.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/data-structure/0005.gif
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/data-structure/0006.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/data-structure/0006.gif
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/interview-questions/0001.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/interview-questions/0001.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/interview-questions/0002.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/interview-questions/0002.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/notes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/notes.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/notes/0001.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/notes/0001.jpeg
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/notes/0002.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/notes/0002.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/notes/0003.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/notes/0003.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/performance-optimization/0001.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/performance-optimization/0001.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/redux-flow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/redux-flow.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/vue/0001.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/vue/0001.jpeg
--------------------------------------------------------------------------------
/docs/.vuepress/public/images/vue/0002.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dengwb1991/vue-press-notes/45573cdce5f0ae29352f2ca141086ca8963bad50/docs/.vuepress/public/images/vue/0002.png
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | home: true
3 | actionText: 开始阅读 →
4 | # actionLink: /zh/guide/
5 | features:
6 | - title:
7 | details:
8 | - title:
9 | details:
10 | - title:
11 | details:
12 | ---
13 |
14 |
15 |
16 |
26 |
27 |
37 |
38 |
48 |
49 |
50 |
51 |
Babel
52 |
53 |
AST抽象语法树
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
73 |
74 |
79 |
80 |
85 |
86 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/docs/babel/001_babel_introduce.md:
--------------------------------------------------------------------------------
1 | # babel介绍
2 |
3 | ## babel的用途
4 |
5 | 1. 代码转译到环境支持的js
6 |
7 | 用来把代码中的 `esnext` 的新的语法、`typescript` 和 `flow` 的语法转成基于目标环境支持的语法的实现。并且还可以把目标环境不支持的 `api` 进行 `polyfill`。
8 |
9 | `babel7` 支持了 `preset-env`,可以指定 `targets` 来进行按需转换,转换更加的精准,产物更小。
10 |
11 | 2. 代码转换你想要的js
12 |
13 | `babel` 是一个转译器,暴露了很多 `api`,用这些 `api` 可以完成代码到 `AST` 的解析、转换、以及目标代码的生成。
14 |
15 | 开发者可以用它来来完成一些特定用途的转换,比如函数插桩(函数中自动插入一些代码,例如埋点代码)、自动国际化等。
16 |
17 | 现在比较流行的小程序转译工具 `taro`,就是基于 `babel` 的 `api` 来实现的。
18 |
19 | 3. 代码静态分析
20 |
21 | 对代码进行 `parse` 之后,能够进行转换,是因为通过 `AST` 的结构能够理解代码。理解了代码之后,除了进行转换然后生成目标代码之外,也同样可以用于分析代码的信息,进行一些检查。
22 |
23 | * `linter` 工具就是分析 `AST` 的结构,对代码规范进行检查。
24 |
25 | * `api` 文档自动生成工具,可以提取源码中的注释,然后生成文档。
26 |
27 | * `type checker` 会根据从 `AST` 中提取的或者推导的类型信息,对 `AST` 进行类型是否一致的检查,从而减少运行时因类型导致的错误。
28 |
29 | * 压缩混淆工具,这个也是分析代码结构,进行删除死代码、变量名混淆、常量折叠等各种编译优化,生成体积更小、性能更优的代码。
30 |
31 | * `js` 解释器,除了对 `AST` 进行各种信息的提取和检查以外,我们还可以直接解释执行 `AST`。
32 |
33 | ## 编译器与转译器
34 |
35 | 编译的定义就是从一种编程语言转成另一种编程语言. 一般编译器 `Compiler` 是指高级语言到低级语言的转换工具,对于高级语言到高级语言的转换工具,被叫做转换编译器,简称转译器 (`Transpiler`)。
36 |
37 | `babel` 就是一个 `Javascript Transpiler`。
38 |
39 | ### babel的编译流程
40 |
41 | `babel` 是 `source to source` 的转换,整体编译流程分为三步:
42 |
43 | * `parse`:通过 `parser` 把源码转成抽象语法树(AST)
44 | * `transform`:遍历 AST,调用各种 `transform` 插件对 AST 进行增删改
45 | * `generate`:把转换后的 AST 打印成目标代码,并生成 `sourcemap`
46 |
47 | ### parse
48 |
49 | parse 阶段的目的是把源码字符串转换成机器能够理解的 AST,这个过程分为**词法分析**、**语法分析**。
50 |
51 | 比如 `let name = 'guang';` 这样一段源码,我们要先把它分成一个个不能细分的单词(**token**),也就是 `let`, `name`, `=`, `'guang'`,这个过程是词法分析,按照单词的构成规则来拆分字符串成单词。
52 |
53 | 之后要把 token 进行递归的组装,生成 AST,这个过程是语法分析,按照不同的语法结构,来把一组单词组合成对象,比如声明语句、赋值表达式等都有对应的 AST 节点。
54 |
55 | ### transform
56 |
57 | transform 阶段是对 parse 生成的 AST 的处理,会进行 AST 的遍历,遍历的过程中处理到不同的 AST 节点会调用注册的相应的 visitor 函数,visitor 函数里可以对 AST 节点进行增删改,返回新的 AST(可以指定是否继续遍历新生成的 AST)。这样遍历完一遍 AST 之后就完成了对代码的修改。
58 |
59 | ### generate
60 |
61 | generate 阶段会把 AST 打印成目标代码字符串,并且会生成 sourcemap。不同的 AST 对应的不同结构的字符串。比如 `IfStatement` 就可以打印成 `if(test) {}` 格式的代码。这样从 AST 根节点进行递归的字符串拼接,就可以生成目标代码的字符串。
62 |
63 | sourcemap 记录了源码到目标代码的转换关系,通过它我们可以找到目标代码中每一个节点对应的源码位置,用于调试的时候把编译后的代码映射回源码,或者线上报错的时候把报错位置映射到源码。
--------------------------------------------------------------------------------
/docs/babel/002_babel_ast.md:
--------------------------------------------------------------------------------
1 | # babel AST
2 |
3 | AST 也是有标准的,JS parser 的 AST 大多是 [estree 标准](https://github.com/estree/estree)
4 |
5 |
6 | ## 常见的AST节点
7 |
8 | AST 是对源码的抽象,字面量、标识符、表达式、语句、模块语法、class 语法都有各自的 AST。我们分别来了解一下:
9 |
10 | ### Literal
11 |
12 | Literal 是字面量的意思,比如 `let name = 'guang'`中,`'guang'` 就是一个字符串字面量 StringLiteral,相应的还有数字字面量 NumericLiteral,布尔字面量 BooleanLiteral,字符串字面量 StringLiteral,正则表达式字面量 RegExpLiteral 等。
13 |
14 | 
15 |
16 | 代码中的字面量很多,babel 就是通过 xxLiteral 来抽象这部分内容的。
17 |
18 | ### Identifier
19 |
20 | Identifer 是标识符的意思,变量名、属性名、参数名等各种声明和引用的名字,都是Identifer。我们知道,JS 中的标识符只能包含字母或数字或下划线(“_”)或美元符号(“$”),且不能以数字开头。这是 Identifier 的词法特点。
21 |
22 | 来尝试分析一下,下面这一段代码里面有多少 Identifier 呢?
23 |
24 | ```js
25 | const name = 'guang';
26 |
27 | function say(name) {
28 | console.log(name);
29 | }
30 |
31 | const obj = {
32 | name: 'guang'
33 | }
34 | ```
35 |
36 | 下面被标注的都为 Identifier
37 |
38 | 
39 |
40 |
41 | ### Statement
42 |
43 | statement 是语句,它是可以独立执行的单位,比如 `break`、`continue`、`debugger`、`return` 或者 `if` 语句、`while` 语句、`for` 语句,还有声明语句,表达式语句等。我们写的每一条可以独立执行的代码都是语句。
44 |
45 | 语句末尾一般会加一个分号分隔,或者用换行分隔。
46 |
47 | 下面这些我们经常写的代码,每一行都是一个 Statement:
48 |
49 | ```js
50 | break;
51 | continue;
52 | return;
53 | debugger;
54 | throw Error();
55 | {}
56 | try {} catch(e) {} finally{}
57 | for (let key in obj) {}
58 | for (let i = 0;i < 10;i ++) {}
59 | while (true) {}
60 | do {} while (true)
61 | switch (v){case 1: break;default:;}
62 | label: console.log();
63 | with (a){}
64 | ```
65 |
66 | 它们对应的 AST 节点如下图所示:
67 |
68 | 
69 |
70 | 语句是代码执行的最小单位,可以说,代码是由语句(Statement)构成的。
71 |
72 | ### Declaration
73 |
74 | 声明语句是一种特殊的语句,它执行的逻辑是在作用域内声明一个变量、函数、class、import、export 等。
75 |
76 | 比如下面这些语句都是声明语句:
77 |
78 | ```js
79 | const a = 1;
80 | function b(){}
81 | class C {}
82 |
83 | import d from 'e';
84 |
85 | export default e = 1;
86 | export {e};
87 | export * from 'e';
88 | ```
89 |
90 | 它们对应的 AST 节点如下图:
91 |
92 | 
93 |
94 | 声明语句用于定义变量,这也是代码中一个基础组成部分。
95 |
96 | ### Expression
97 |
98 | expression 是表达式,特点是执行完以后有返回值,这是和语句 (statement) 的区别。
99 |
100 | 下面是一些常见的表达式
101 |
102 | ```js
103 | [1,2,3]
104 | a = 1
105 | 1 + 2;
106 | -1;
107 | function(){};
108 | () => {};
109 | class{};
110 | a;
111 | this;
112 | super;
113 | a::b;
114 | ```
115 |
116 | 它们对应的 AST 如图:
117 |
118 | 
119 |
120 | :::tip
121 | 我们判断 AST 节点是不是某种类型要看它是不是符合该种类型的特点,比如语句的特点是能够单独执行,表达式的特点是有返回值。
122 | :::
123 |
124 | 有的表达式可以单独执行,符合语句的特点,所以也是语句,比如赋值表达式、数组表达式等,但有的表达式不能单独执行,需要和其他类型的节点组合在一起构成语句。比如匿名函数表达式和匿名 class 表达式单独执行会**报错**:
125 |
126 | ```js
127 | function(){};
128 | class{}
129 | ```
130 |
131 | 需要和其他部分一起构成一条语句,比如组成赋值语句
132 |
133 | ```js
134 | a = function() {}
135 | b = class{}
136 | ```
137 |
138 | 
139 |
140 | 表达式作为语句执行的时候,你会发现解析出的 AST 包裹了一层 ExpressionStatement 节点,代表这个表达式是被当成语句执行的。
141 |
142 | 
143 |
144 | ### Class
145 |
146 | class 的语法也有专门的 AST 节点来表示。
147 |
148 | 整个 class 的内容是 ClassBody,属性是 ClassProperty,方法是ClassMethod(通过 kind 属性来区分是 constructor 还是 method)。
149 |
150 | 比如下面的代码
151 |
152 | ```js
153 | class Guang extends Person{
154 | name = 'guang';
155 | constructor() {}
156 | eat() {}
157 | }
158 | ```
159 |
160 | 对应的AST是这样的
161 |
162 | 
163 |
164 | 详细:
165 |
166 | 
167 |
168 | class 是 es next 的语法,babel 中有专门的 AST 来表示它的内容。
169 |
170 | ### Modules
171 |
172 | es module 是语法级别的模块规范,所以也有专门的 AST 节点。
173 |
174 | ### import
175 |
176 | import 有 3 种语法:
177 |
178 | ```js
179 | // 1. named import
180 | import {c, d} from 'c';
181 |
182 | // 2. default import
183 | import a from 'a';
184 |
185 | // 3. namespaced import
186 | import * as b from 'b';
187 | ```
188 |
189 | 这 3 种语法都对应 `ImportDeclaration` 节点,但是 `specifiers` 属性不同,分别对应 `ImportSpicifier`、`ImportDefaultSpecifier`、`ImportNamespaceSpcifier`。
190 |
191 | 
192 |
193 | 图中黄框标出的就是 specifier 部分。可以直观的看出整体结构相同,只是 specifier 部分不同,所以 import 语法的 AST 的结构是 ImportDeclaration 包含着各种 import specifier。
194 |
195 | ### export
196 |
197 | export 也有3种语法:
198 |
199 | ```js
200 | // 1. named export:
201 | export { b, d};
202 |
203 | // 2. default export:
204 | export default a;
205 |
206 | // 3. all export:
207 | export * from 'c';
208 | ```
209 | 分别对应 ExportNamedDeclaration、ExportDefaultDeclaration、ExportAllDeclaration 的 AST。
210 |
211 | 
212 |
213 | ### Program & Directive
214 |
215 | program 是代表整个程序的节点,它有 body 属性代表程序体,存放 statement 数组,就是具体执行的语句的集合。还有 directives 属性,存放 Directive 节点,比如 `"use strict"` 这种指令会使用 Directive 节点表示。
216 |
217 | 
218 |
219 | Program 是包裹具体执行语句的节点,而 Directive 则是代码中的指令部分。
220 |
221 | ### File & Comment
222 |
223 | babel 的 AST 最外层节点是 File,它有 program、comments、tokens 等属性,分别存放 Program 程序体、注释、token 等,是最外层节点。
224 |
225 | 注释分为块注释和行内注释,对应 CommentBlock 和 CommentLine 节点。
226 |
227 | 
228 |
229 | 上面 6 种就是常见的一些 AST 节点类型,babel 就是通过这些节点来抽象源码中不同的部分。
230 |
231 | ## AST 可视化查看工具
232 |
233 | [astexplorer](https://astexplorer.net/)
234 |
235 | 可切换 parser 为 @babel/parser
236 |
237 | 如果想查看全部的 AST 可以在[babel parser](https://github.com/babel/babel/blob/main/packages/babel-parser/ast/spec.md) 仓库里的 AST 文档里查,
238 | 或者直接去看 @babel/types 的 [typescript 类型定义](https://github.com/babel/babel/blob/main/packages/babel-types/src/ast-types/generated/index.ts)。
239 |
240 | ## AST 的公共属性
241 |
242 | 每种 AST 都有自己的属性,但是它们也有一些公共的属性:
243 |
244 | * `type`: AST 节点的类型
245 | * `start、end、loc`: start 和 end 代表该节点在源码中的开始和结束下标。而 loc 属性是一个对象,有 line 和 column 属性分别记录开始和结束的行列号。
246 | * `leadingComments、innerComments、trailingComments`: 表示开始的注释、中间的注释、结尾的注释,每个 AST 节点中都可能存在注释,而且可能在开始、中间、结束这三种位置,想拿到某个 AST 的注释就通过这三个属性。
247 | * `extra`: 记录一些额外的信息,用于处理一些特殊情况。比如 StringLiteral 的 value 只是值的修改,而修改 extra.raw 则可以连同单双引号一起修改。
248 |
249 | 
250 |
251 | ## 总结
252 |
253 | 本节介绍了,标识符(Identifer)、字面量(Literal)、语句(Statement)、声明语句(Declaration)、表达式(Expression),以及 Class、Modules、File、Program、Directive、Comment 这些 AST 节点。
254 |
255 | AST 节点可能同时有多种类型,确定一种 AST 节点是什么类型主要看它的特点,比如 Statement 的特点是可以单独执行,Expression 的特点是有返回值,所以一些可以单独执行的 Expression 会包一层 ExpressionStatement。
--------------------------------------------------------------------------------
/docs/babel/003_babel_api.md:
--------------------------------------------------------------------------------
1 | # babel API
2 |
3 | ## babel 的 api 有哪些
4 |
5 | `babel` 的编译流程分为三步:`parse`、`transform`、`generate`,每一步都暴露了一些 api 出来。
6 |
7 | * **parse** 阶段有 `@babel/parser`,功能是把源码转成 AST
8 | * **transform** 阶段有 `@babel/traverse`,可以遍历 AST,并调用 visitor 函数修改 AST,修改 AST 自然涉及到 AST 的判断、创建、修改等,这时候就需要 `@babel/types` 了,当需要批量创建 AST 的时候可以使用 `@babel/template` 来简化 AST 创建逻辑。
9 | * **generate** 阶段会把 AST 打印为目标代码字符串,同时生成 sourcemap,需要 `@babel/generator` 包
10 | * 中途遇到错误想打印代码位置的时候,使用 `@babel/code-frame` 包
11 | * babel 的整体功能通过 `@babel/core` 提供,基于上面的包完成 babel 整体的编译流程,并应用 plugin 和 preset。
12 |
13 | 以上相关包均可在 [babel 文档](https://babeljs.io/docs/en/babel-parser#options) 内查看.
14 |
15 | ## @babel/parser
16 |
17 | babel parser 叫 babylon,是基于 acorn 实现的,扩展了很多语法,可以支持 es next(现在支持到 es2020)、jsx、flow、typescript 等语法的解析。
18 |
19 | babel parser 默认只能 parse js 代码,jsx、flow、typescript 这些非标准的语法的解析需要指定语法插件。
20 |
21 | 它提供了有两个 api:parse 和 parseExpression。两者都是把源码转成 AST,不过 parse 返回的 AST 根节点是 File(整个 AST),parseExpression 返回的 AST 根节点是 Expression(表达式的 AST),粒度不同。
22 |
23 | ```typescript
24 | function parse(input: string, options?: ParserOptions): File
25 | function parseExpression(input: string, options?: ParserOptions): Expression
26 | ```
27 |
28 | 详细的 options 可以查看[文档](https://babeljs.io/docs/en/babel-parser#options)。其实主要分为两类,一是 parse 的内容是什么,二是以什么方式去 parse
29 |
30 | ### parse 的内容是什么
31 |
32 | * `plugins`: 指定jsx、typescript、flow 等插件来解析对应的语法
33 |
34 | * `allowXxx`: 指定一些语法是否允许,比如函数外的 await、没声明的 export等
35 |
36 | * `sourceType`: 指定是否支持解析模块语法,有 module、script、unambiguous 3个取值:
37 | 1) module:解析 es module 语法
38 | 2) script:不解析 es module 语法
39 | 3) unambiguous:根据内容是否有 import 和 export 来自动设置 module 还是 script
40 | 一般我们会指定 sourceType 为 unambiguous。
41 |
42 | ```js
43 | const parser = require('@babel/parser');
44 |
45 | const ast = parser.parse("代码", {
46 | sourceType: 'unambiguous',
47 | plugins: ['jsx']
48 | });
49 | ```
50 |
51 | ### 以什么方式 parse
52 |
53 | * strictMode 是否是严格模式
54 |
55 | * startLine 从源码哪一行开始 parse
56 |
57 | * errorRecovery 出错时是否记录错误并继续往下 parse
58 |
59 | * tokens parse 的时候是否保留 token 信息
60 |
61 | * ranges 是否在 ast 节点中添加 ranges 属性
62 |
63 | ## @babel/traverse
64 |
65 | parse 出的 AST 由 @babel/traverse 来遍历和修改,babel traverse 包提供了 traverse 方法:
66 |
67 | ```js
68 | function traverse(parent, opts)
69 | ```
70 |
71 | 常用的就前面两个参数,parent 指定要遍历的 AST 节点,opts 指定 visitor 函数。babel 会在遍历 parent 对应的 AST 时调用相应的 visitor 函数。
72 |
73 |
74 | ### 遍历过程
75 |
76 | visitor 是指定对什么 AST 做什么处理的函数,babel 会在遍历到对应的 AST 时回调它们。
77 |
78 | 而且可以指定刚开始遍历(enter)和遍历结束后(exit)两个阶段的回调函数,
79 |
80 | 比如:
81 |
82 | ```js
83 | traverse(ast, {
84 | FunctionDeclaration: {
85 | enter(path, state) {}, // 进入节点时调用
86 | exit(path, state) {} // 离开节点时调用
87 | }
88 | })
89 | ```
90 |
91 | 如果只指定了一个函数,那就是 enter 阶段会调用的:
92 |
93 | ```js
94 | traverse(ast, {
95 | FunctionDeclaration(path, state) {} // 进入节点时调用
96 | })
97 | ```
98 |
99 | enter 时调用是在遍历当前节点的子节点前调用,exit 时调用是遍历完当前节点的子节点后调用。
100 |
101 | 
102 |
103 | 而且同一个 visitor 函数可以用于多个 AST 节点的处理,方式是指定一系列 AST,用 | 连接:
104 |
105 | ```js
106 | // 进入 FunctionDeclaration 和 VariableDeclaration 节点时调用
107 | traverse(ast, {
108 | 'FunctionDeclaration|VariableDeclaration'(path, state) {}
109 | })
110 | ```
111 |
112 | 此外,AST 还有别名的,比如各种 XxxStatement 有个 Statement 的别名,各种 XxxDeclaration 有个 Declaration 的别名,那自然可以通过别名来指定对这些 AST 的处理:
113 |
114 | ```js
115 | // 通过别名指定离开各种 Declaration 节点时调用
116 | traverse(ast, {
117 | Declaration: {
118 | exit(path, state) {}
119 | }
120 | })
121 | ```
122 |
123 | 具体的别名有哪些在[babel-types](https://github.com/babel/babel/blob/main/packages/babel-types/src/ast-types/generated/index.ts#L2059) 的类型定义可以查。
124 |
125 | ### visitor 中的 path
126 |
127 | AST 是棵树,遍历过程中肯定是有个路径的,path 就记录了这个路径:
128 |
129 | 
130 |
131 | 如图,节点 1、节点 2、节点 3 是三层 AST,通过两个 path 关联了起来,
132 |
133 | path1 就关联了节点 1 和 节点 2,记录了节点 1 是父节点,节点 2 是子节点。
134 |
135 | path2 关联了节点 2 和节点 3,记录了节点 2 是父节点,节点 3 是子节点。
136 |
137 | 而且 path1 和 path2 还有父子关系。
138 |
139 | 通过这样的 path 对象,那不就把遍历的路径串联起来了么。
140 |
141 | 而且,最重要的是 path 有很多属性和方法,比如记录父子、兄弟等关系的:
142 |
143 | * path.node 指向当前 AST 节点
144 | * path.parent 指向父级 AST 节点
145 | * path.getSibling、path.getNextSibling、path.getPrevSibling 获取兄弟节点
146 | * path.find 从当前节点向上查找节点
147 | * path.get、path.set 获取 / 设置属性的 path
148 |
149 | 还有作用域相关的:
150 |
151 | * path.scope 获取当前节点的作用域信息
152 |
153 | 判断 AST 类型的:
154 |
155 | * path.isXxx 判断当前节点是不是 xx 类型
156 | * path.assertXxx 判断当前节点是不是 xx 类型,不是则抛出异常
157 |
158 | 增删改 AST 的:
159 |
160 | * path.insertBefore、path.insertAfter 插入节点
161 | * path.replaceWith、path.replaceWithMultiple、replaceWithSourceString 替换节点
162 | * path.remove 删除节点
163 |
164 | 跳过遍历的:
165 |
166 | * path.skip 跳过当前节点的子节点的遍历
167 | * path.stop 结束后续遍历
168 |
169 | path 的 api 是学习 babel 插件最核心的。
170 |
171 | 
172 |
173 | 上面罗列了一些常用的 api,可以通过这些 api 完成对 AST 的操作。当然,path 的 api 不是只有这些.
174 |
175 | ### visitor 中的 state
176 |
177 | 第二个参数 state 则是遍历过程中在不同节点之间传递数据的机制,插件会通过 state 传递 options 和 file 信息,我们也可以通过 state 存储一些遍历过程中的共享数据。
178 |
179 | 
180 |
181 | ## @babel/types
182 |
183 | 遍历 AST 的过程中需要创建一些 AST 和判断 AST 的类型,这时候就需要 `@babel/types` 包。
184 |
185 | 举例来说,如果要创建IfStatement就可以调用
186 |
187 | ```js
188 | t.ifStatement(test, consequent, alternate);
189 | ```
190 |
191 | 而判断节点是否是 IfStatement 就可以调用 isIfStatement 或者 assertIfStatement
192 |
193 | ```js
194 | t.isIfStatement(node, opts);
195 | t.assertIfStatement(node, opts);
196 | ```
197 |
198 | opts 可以指定一些属性是什么值,增加更多限制条件,做更精确的判断。
199 |
200 | ```js
201 | t.isIdentifier(node, { name: "paths" })
202 | ```
203 |
204 | isXxx 和 assertXxx 看起来很像,但是功能不大一样:isXxx 会返回 boolean,而 assertXxx 则会在类型不一致时抛异常。
205 |
206 | 所有的 AST 的 build、assert 的 api 可以在 [babel types 文档](https://babeljs.io/docs/en/babel-types#api)中查。
207 |
208 | ## @babel/template
209 |
210 | 通过 @babel/types 创建 AST 还是比较麻烦的,要一个个的创建然后组装,如果 AST 节点比较多的话需要写很多代码,这时候就可以使用 `@babel/template` 包来批量创建。
211 |
212 | 这个包有这些 api:
213 |
214 | ```js
215 | const ast = template(code, [opts])(args);
216 | const ast = template.ast(code, [opts]);
217 | const ast = template.program(code, [opts]);
218 | ```
219 |
220 | 这些都是传入一段字符串,返回创建好的 AST,区别只是返回的 AST 粒度不大一样:
221 |
222 | * template.ast 返回的是整个 AST。
223 |
224 | * template.program 返回的是 Program 根节点。
225 |
226 | * template.expression 返回创建的 expression 的 AST。
227 |
228 | * template.statements 返回创建的 statems 数组的 AST。
229 |
230 | 用 template.ast 创建的 Expression 会被包裹一层 ExpressionStatement 节点,而 template.expression 方法创建的 AST 就不会。
231 |
232 | 所以,当你明确知道了创建的 AST 的类型的话,用更细粒度的 api 会方便一些。
233 |
234 | 模版也支持占位符,可以在模版里设置一些占位符,调用时再传入这些占位符参数对应的 AST 节点。
235 |
236 | ```js
237 | const fn = template(`console.log(NAME)`);
238 |
239 | const ast = fn({
240 | NAME: t.stringLiteral("guang"),
241 | });
242 |
243 | // 或者
244 |
245 | const fn = template(`console.log(%%NAME%%)`);
246 |
247 | const ast = fn({
248 | NAME: t.stringLiteral("guang"),
249 | });
250 | ```
251 |
252 | 这样就是通过模版来批量创建 AST,但是其中的占位符是用传入的 AST。
253 |
254 | 加不加 %% 都行,当占位符和其他变量名冲突时可以加上。
255 |
256 | ## @babel/generator
257 |
258 | AST 转换完之后就要打印成目标代码字符串,通过 `@babel/generator` 包的 generate api
259 |
260 | ```js
261 | function (ast: Object, opts: Object, code: string): { code, map }
262 | ```
263 |
264 | 第一个参数是要打印的 AST。
265 |
266 | 第二个参数是 options,指定打印的一些细节,比如通过 comments 指定是否包含注释,通过 minified 指定是否包含空白字符。
267 |
268 | 第三个参数当多个文件合并打印的时候需要用到,这部分直接看[文档](https://babeljs.io/docs/en/babel-generator)即可,基本用不到。
269 |
270 | options 中常用的是 sourceMaps,开启了这个选项才会生成 sourcemap。
271 |
272 | ```js
273 | import generate from "@babel/generator";
274 |
275 | const { code, map } = generate(ast, { sourceMaps: true })
276 | ```
277 |
278 | ## @babel/code-frame
279 |
280 | babel 的报错一半都会直接打印错误位置的代码,而且还能高亮,
281 |
282 | 我们打印错误信息的时候也可以用,就是 `@babel/code-frame` 这个包。
283 |
284 | ```js
285 | const result = codeFrameColumns(rawLines, location, {
286 | /* options */
287 | });
288 | ```
289 |
290 | options 可以设置 highlighted (是否高亮)、message(展示啥错误信息)。
291 |
292 | 比如:
293 |
294 | ```js
295 | const { codeFrameColumns } = require("@babel/code-frame");
296 |
297 | try {
298 | throw new Error("xxx 错误");
299 | } catch (err) {
300 | console.error(codeFrameColumns(`const name = guang`, {
301 | start: { line: 1, column: 14 }
302 | }, {
303 | highlightCode: true,
304 | message: err.message
305 | }));
306 | }
307 | ```
308 |
309 | 这种控制台打印代码格式的功能就叫做 code frame。
310 |
311 | ## @babel/core
312 |
313 | 前面讲了 `@babel/parser`、`@babel/traverse`、`@babel/generaotr`、`@babel/types`、`@babel/template` 等包,babel 的功能就是通过这些包来实现的。
314 |
315 | babel 基于这些包来实现编译、插件、预设等功能的包就是 @babel/core。
316 |
317 | 这个包的功能就是完成整个编译流程,从源码到目标代码,生成 sourcemap。实现 plugin 和 preset 的调用。
318 |
319 | api 也有好几个:
320 |
321 | ```js
322 | transformSync(code, options); // => { code, map, ast }
323 |
324 | transformFileSync(filename, options); // => { code, map, ast }
325 |
326 | transformFromAstSync(
327 | parsedAst,
328 | sourceCode,
329 | options
330 | ); // => { code, map, ast }
331 | ```
332 |
333 | 比如这三个 transformXxx 的 api 分别是从源代码、源代码文件、源代码 AST 开始处理,最终生成目标代码和 sourcemap。
334 |
335 | options 主要配置 plugins 和 presets,指定具体要做什么转换。
336 |
337 | 这些 api 也同样提供了异步的版本,异步地进行编译,返回一个 promise
338 |
339 | ```js
340 | transformAsync("code();", options).then(result => {})
341 | transformFileAsync("filename.js", options).then(result => {})
342 | transformFromAstAsync(parsedAst, sourceCode, options).then(result => {})
343 | ```
344 |
345 | 注意:不带 sync、async 的 api 已经被标记过时了,也就是 transformXxx 这些,后续会删掉,不建议用,直接用 transformXxxSync 和 transformXxxAsync。也就是明确是同步还是异步。
346 |
347 | @babel/core 支持 plugin 和 preset,一般我们配置的都是对象的格式,其实也有一个 api 来创建,也就是 createConfigItem:
348 |
349 | ```js
350 | createConfigItem(value, options) // configItem
351 | ```
352 |
353 | 不过用和不用的没啥区别,常用的还是直接写配置对象。
354 |
355 | ## 总结
356 |
357 | 这一节我们了解了编译过程中各阶段的 api:
358 |
359 | * @babel/parser 对源码进行 parse,可以通过 plugins、sourceType 等来指定 parse 语法
360 |
361 | * @babel/traverse 通过 visitor 函数对遍历到的 ast 进行处理,分为 enter 和 exit 两个阶段,具体操作 AST 使用 path 的 api,还可以通过 state 来在遍历过程中传递一些数据
362 |
363 | * @babel/types 用于创建、判断 AST 节点,提供了 xxx、isXxx、assertXxx 的 api
364 |
365 | * @babel/template 用于批量创建节点
366 |
367 | * @babel/code-frame 可以创建友好的报错信息
368 |
369 | * @babel/generator 打印 AST 成目标代码字符串,支持 comments、minified、sourceMaps 等选项。
370 |
371 | * @babel/core 基于上面的包来完成 babel 的编译流程,可以从源码字符串、源码文件、AST 开始。
--------------------------------------------------------------------------------
/docs/babel/004_demo_insert_params.md:
--------------------------------------------------------------------------------
1 | # 示例-插入函数调用参数
2 |
3 | ## 背景
4 |
5 | 我们经常会打印一些日志来辅助调试,但是有的时候会不知道日志是在哪个地方打印的。
6 |
7 | ## 目标
8 |
9 | 希望通过 babel 能够自动在 console.log 等 api 中插入文件名和行列号的参数,方便定位到代码。
10 |
11 | ```js
12 | // 现状
13 | console.log(0);
14 |
15 | // 目标
16 | console.log('文件名(行号,列号):', 0);
17 | ```
18 |
19 | ## 实现思路
20 |
21 | 使用 [astexplorer.net](https://astexplorer.net) 分析
22 |
23 | 
24 |
25 | 函数调用表达式的 AST 是 CallExpression。
26 |
27 | 我们要做的是在遍历 AST 的时候对 console.log、console.info 等 api 自动插入一些参数,也就是要通过 `visitor` 指定对 CallExpression 的 AST 做一些修改。
28 |
29 | CallExrpession 节点有两个属性,callee 和 arguments,分别对应调用的函数名和参数, 所以我们要判断当 callee 是 console.xx 时,在 arguments 的数组中中插入一个 AST 节点。
30 |
31 | 
32 |
33 | ## 代码实现
34 |
35 | 需要安装 `@babel/parser`、`@babel/traverse`、`@babel/generator`, 编译流程是 `parse`、`transform`、`generate`,整体框架如下:
36 |
37 | ```js
38 | const parser = require('@babel/parser')
39 | const traverse = require('@babel/traverse').default
40 | const generate = require('@babel/generator').default
41 |
42 | const sourceCode = `console.log(0);`
43 |
44 | const ast = parser.parse(sourceCode, {
45 | sourceType: 'unambiguous'
46 | })
47 |
48 | traverse(ast, {
49 | CallExpression(path, state) {
50 |
51 | }
52 | })
53 |
54 | const { code, map } = generate(ast)
55 |
56 | console.log(code)
57 | ```
58 |
59 | :::tip
60 | `ES module` 导出的包,通过 `commonjs` 方式引入时需要取 `default` 属性
61 | :::
62 |
63 | `parser` 需要知道代码是不是符合 ES module 规范,需要给 options.sourceType 设置值为 `module`、`script`、`unambiguous`,`unambiguous` 会根据内容是否包含 import、export 来自动设置
64 |
65 |
66 | 接下来将 `sourceCode` 设计复杂一些,如下:
67 |
68 | ```js
69 | const sourceCode = `
70 | console.log(1);
71 |
72 | function func() {
73 | console.info(2);
74 | }
75 |
76 | export default class Clazz {
77 | say() {
78 | console.debug(3);
79 | }
80 | render() {
81 | return {console.error(4)}
82 | }
83 | }
84 | `;
85 | ```
86 |
87 | 这里用到了 jsx 的语法,所以 parser 要开启 jsx 的 plugin。
88 |
89 | ```js
90 | const ast = parser.parse(sourceCode, {
91 | sourceType: 'unambiguous',
92 | plugins: ['jsx']
93 | });
94 | ```
95 |
96 | 我们要修改 CallExpression 的 AST,如果是 console.xxx 的 api,那就在 arguments 中插入行列号的参数:
97 |
98 | ```js
99 | const parser = require('@babel/parser');
100 | const traverse = require('@babel/traverse').default;
101 | const types = require('@babel/types');
102 |
103 | const ast = parser.parse(sourceCode, {
104 | sourceType: 'unambiguous',
105 | plugins: ['jsx']
106 | });
107 |
108 | traverse(ast, {
109 | CallExpression (path, state) {
110 | if ( types.isMemberExpression(path.node.callee)
111 | && path.node.callee.object.name === 'console'
112 | && ['log', 'info', 'error', 'debug'].includes(path.node.callee.property.name)
113 | ) {
114 | const { line, column } = path.node.loc.start;
115 | path.node.arguments.unshift(types.stringLiteral(`filename: (${line}, ${column})`))
116 | }
117 | }
118 | });
119 | ```
120 |
121 | `types.isMemberExpression` 判断当前 callee 的类型是否为 `MemberExpression`
122 |
123 | `path.node` 获取当前 node 对象,其中包含 `loc`、`callee`、`arguments` 等
124 |
125 | 效果
126 |
127 | ```js
128 | // node .js
129 |
130 | console.log("filename: (2, 0)", 1);
131 | function func() {
132 | console.info("filename: (5, 4)", 2);
133 | }
134 | export default class Clazz {
135 | say() {
136 | console.debug("filename: (10, 8)", 3);
137 | }
138 | render() {
139 | return {console.error("filename: (13, 21)", 4)}
;
140 | }
141 | }
142 | ```
143 |
144 | [源码](https://github.com/dengwb1991/lifelong-learning/blob/master/babel/001_exercize-parameters-insert/src/demo1.js)
145 |
146 |
147 | ### 优化
148 |
149 | 对判断条件语法进行简化,把 callee 的 AST 打印成字符串后再判断:
150 |
151 | ```js
152 | const targetCalleeName = ['log', 'info', 'error', 'debug'].map(item => `console.${item}`);
153 |
154 | traverse(ast, {
155 | CallExpression(path, state) {
156 | const calleeName = generate(path.node.callee).code;
157 | // const calleeName = path.get('callee').toString()
158 | if (targetCalleeName.includes(calleeName)) {
159 | const { line, column } = path.node.loc.start;
160 | path.node.arguments.unshift(types.stringLiteral(`filename: (${line}, ${column})`))
161 | }
162 | }
163 | });
164 | ```
165 |
166 | 不调用 `generate` 的话,也可以使用 `path.get('callee').toString()`
167 |
168 | ## 需求变更
169 |
170 | ### 目标
171 |
172 | 期望在节点之前打印位置;
173 |
174 | ```js
175 | console.log(1)
176 |
177 | // 期望转换为
178 |
179 | console.log('文件名(行号,列号):')
180 | console.log(1)
181 | ```
182 |
183 | ### 实现思路
184 |
185 | 1. 这个需求的改动只是从插入一个参数变成了在当前 console.xx 的 AST 之前插入一个 console.log 的 AST,整体流程还是一样。
186 |
187 | 2. JSX 中的 console 代码不能简单的在前面插入一个节点,而要把整体替换成一个数组表达式,因为 JSX 中只支持写单个表达式。
188 |
189 | ```js
190 | {console.log(111)}
191 |
192 | {[console.log('filename.js(11,22)'), console.log(111)]}
193 | ```
194 |
195 | 因为 `{}` 里只能是表达式,这个 AST 叫做 JSXExpressionContainer,表达式容器。
196 |
197 | ### 代码实现
198 |
199 | ```js
200 | if (path.findParent(path => path.isJSXElement())) {
201 | path.replaceWith(types.arrayExpression([newNode, path.node]))
202 | path.skip();// 跳过子节点处理
203 | } else {
204 | path.insertBefore(newNode);
205 | }
206 | ```
207 |
208 | `path.insertBefore` 插入 AST
209 |
210 | `path.replaceWith` 替换 AST
211 |
212 | `path.findParent` 遍历 AST
213 |
214 | `path.isJSXElement` 是否为 JSXElement
215 |
216 | `path.skip` 跳过新节点的遍历
217 |
218 | ### 完整代码
219 |
220 | ```js
221 | const parser = require('@babel/parser');
222 | const traverse = require('@babel/traverse').default;
223 | const generate = require('@babel/generator').default;
224 | const types = require('@babel/types');
225 | const template = require('@babel/template').default;
226 |
227 | const sourceCode = `
228 | console.log(1);
229 |
230 | function func() {
231 | console.info(2);
232 | }
233 |
234 | export default class Clazz {
235 | say() {
236 | console.debug(3);
237 | }
238 | render() {
239 | return {console.error(4)}
240 | }
241 | }
242 | `
243 |
244 | const ast = parser.parse(sourceCode, {
245 | sourceType: 'unambiguous',
246 | plugins: ['jsx']
247 | });
248 |
249 | const targetCalleeName = ['log', 'info', 'error', 'debug'].map(item => `console.${item}`);
250 |
251 | traverse(ast, {
252 | CallExpression(path, state) {
253 | if (path.node.isNew) {
254 | return;
255 | }
256 | const calleeName = generate(path.node.callee).code;
257 | if (targetCalleeName.includes(calleeName)) {
258 | const { line, column } = path.node.loc.start;
259 |
260 | const newNode = template.expression(`console.log("filename: (${line}, ${column})")`)();
261 | newNode.isNew = true;
262 |
263 | if (path.findParent(path => path.isJSXElement())) {
264 | path.replaceWith(types.arrayExpression([newNode, path.node]))
265 | path.skip();
266 | } else {
267 | path.insertBefore(newNode);
268 | }
269 | }
270 | }
271 | });
272 |
273 | const { code, map } = generate(ast)
274 |
275 | console.log(code)
276 |
277 | // console.log("filename: (2, 0)")
278 | // console.log(1);
279 | // function func() {
280 | // console.log("filename: (5, 4)")
281 | // console.info(2);
282 | // }
283 | // export default class Clazz {
284 | // say() {
285 | // console.log("filename: (10, 8)")
286 | // console.debug(3);
287 | // }
288 | // render() {
289 | // return {[console.log("filename: (13, 21)"), console.error(4)]}
;
290 | // }
291 | // }
292 | ```
293 |
294 | ## 改造成 babel 插件
295 |
296 | ### 实现思路
297 |
298 | 首先 `babel` 支持 `transform` 插件,如下:
299 |
300 | ```js
301 | module.exports = function (api, options) {
302 | return {
303 | visitor: {
304 | Identifier (path, state) {}
305 | }
306 | }
307 | }
308 | ```
309 |
310 | `babel` 插件的形式就是函数返回一个对象,对象有 `visitor` 属性;
311 |
312 | 函数的第一个参数( `api` ) 可以拿到 `types`、`template` 等常用的包的 API,这样我们就不需要单独引入这些依赖包了;
313 |
314 | 作为插件使用的时候,并不需要自己调用 `parse`、`traverse`、`generate`,这些都是通用流程,babel 会帮助做,我们只需要提供一个 `visitor` 函数,在这个函数内完成转换功能就可以了;
315 |
316 | 函数的第二个参数( `options` ) 可以拿到插件的配置信息,比如 `filename`,在上面代码 `state` 中,可以拿到 `options`;
317 |
318 | ### 代码实现
319 |
320 | ```js
321 | const targetCalleeName = ['log', 'info', 'error', 'debug'].map(item => `console.${item}`);
322 |
323 | const parametersInsertPlugin = ({ types, template }, options, dirname) => {
324 | return {
325 | visitor: {
326 | CallExpression(path, state) {
327 | if (path.node.isNew) {
328 | return;
329 | }
330 | const calleeName = path.get('callee').toString();
331 |
332 | if (targetCalleeName.includes(calleeName)) {
333 | const { line, column } = path.node.loc.start;
334 | const newNode = template.expression(`console.log("${state.filename || 'unkown filename'}: (${line}, ${column})")`)();
335 | newNode.isNew = true;
336 |
337 | if (path.findParent(path => path.isJSXElement())) {
338 | path.replaceWith(types.arrayExpression([newNode, path.node]))
339 | path.skip();
340 | } else {
341 | path.insertBefore(newNode);
342 | }
343 | }
344 | }
345 | }
346 | }
347 | }
348 | module.exports = parametersInsertPlugin;
349 | ```
350 |
351 | 然后通过 `@babel/core` 的 `transformSync` 方法来编译代码,并引入上面的插件:
352 |
353 | ```js
354 | const { transformFileSync } = require('@babel/core');
355 | const insertParametersPlugin = require('./plugin/parameters-insert-plugin');
356 | const path = require('path');
357 |
358 | const { code } = transformFileSync(path.join(__dirname, './sourceCode.js'), {
359 | plugins: [insertParametersPlugin],
360 | parserOpts: {
361 | sourceType: 'unambiguous',
362 | plugins: ['jsx']
363 | }
364 | });
365 |
366 | console.log(code);
367 | ```
368 |
369 | 这样我们成功就把前面调用 parse、traverse、generate 的代码改造成了 babel 插件的形式,只需要提供一个转换函数,traverse 的过程中会自动调用。
370 |
371 | [源码](https://github.com/dengwb1991/lifelong-learning/blob/master/babel/001_exercize-parameters-insert/src/demo3/index.js)
372 |
373 | ## 总结
374 |
375 | 首先通过 `@babel/parser`、`@babel/traverse`、`@babel/generator` 来组织编译流程,通过 `@babel/types` 创建AST,通过 path 的各种 api 对 AST 进行操作。
376 |
377 | 后来需求改为在前面插入 console.xxx 的方式,我们引入了 `@babel/template` 包,通过 path.replaceWith 和 path.insertBefore 来对 AST 做插入和替换,需要通过 path.findParent 来判断 AST 的父元素是否包含 JSXElement 类型的 AST。子节点的 AST 要用 path.skip 跳过遍历,而且要对新的 AST 做标记,跳过对新生成的节点的处理。
378 |
379 | 之后我们把它改造成了 babel 插件,也就是一个函数返回一个对象的格式,函数的第一个参数可以拿到各种 babel 常用包的 api,比如 types、template。 插件不需要调用 parse、traverse、generate 等 api,只需要提供 visitor 函数。最后我们通过 @babel/core 的 api 使用了下这个插件。
--------------------------------------------------------------------------------
/docs/babel/005_history_of_js_parser.md:
--------------------------------------------------------------------------------
1 | # JS Parser 的历史
2 |
3 | > babel 的 parser 是基于 `acorn` 扩展而来的,而 `acorn` 也不是最早的 js parser
4 |
5 | ## SpiderMonkey 和 estree 标准
6 |
7 | 在 nodejs 出现之后,前端可以用 nodejs 来做一些工程化的事情,工程化需要对代码做编译、压缩、lint 等处理,也就有了对 js parser 的需求。
8 |
9 | Mozilla 在 MDN 上公布了火狐浏览器的 JS 引擎 SpiderMonkey(c++ 写的 js 引擎)的 parser api 和 AST 标准,所以当时最早的 JS parser ---- [esprima](https://github.com/jquery/esprima) 就是基于 SpiderMonkey 的 AST 标准来实现的,后来形成了 [estree 标准](https://github.com/estree/estree)。 当时很多的前端领域的工具都基于 esprima。
10 |
11 | 但是到了 2015 年之后,es 标准一年一个版本,而 esprima 的更新速度跟不上,它跟不上也就导致了依赖它的一系列工具都跟不上,所以 eslint 就 fork 了一份 esprima,做了一些扩展,来自己实现新语法的 parse,这就是 espree,它依然是 estree 标准的,这是 eslint 的默认 parser。
12 |
13 | ## acorn
14 |
15 | 后面出现了 [acorn](https://github.com/acornjs/acorn),也是 estree 标准的实现,但是他的速度比 esprima 快,而且支持插件,可以通过插件扩展语法支持。正是速度快加上支持插件让很多工具都转而使用 acorn。
16 |
17 | eslint 的 parser,也就是 [espree](https://github.com/eslint/espree) 本来是 fork 自 esprima,但后来 espree 2.0 基于 acorn 重新实现了,也使用 acorn 的插件机制来扩展语法。
18 |
19 | babel parser(babylon) 也选择了基于 acorn 来实现自己的 parser。
20 |
21 | 但它对 estree 标准的 AST 节点和属性都做了扩展,也提供了一些支持 typescript、jsx、flow 的语法插件(就是我们可以在 @babel/parser 的 plugins 里面指定的那些)。
22 |
23 | 这些 parser 之间的关系如图所示:
24 |
25 | 
26 |
27 | > estree 标准是基于 SpiderMonkey 的 AST 标准扩展的,它的实现有 esprima、acorn 等。
28 | >
29 | > 因为 acorn 新特性支持的全,而且支持插件,所以 espree、babel parser 都是基于 acorn 做了些扩展。
30 | >
31 | > 当然也不是所有的 js parser 都是 estree 标准的,比如 terser、typescript 等都有自己的 AST 标准。
32 |
33 | ## babel parser 对 estree AST 的扩展
34 |
35 | 其实这些可以在 [babel parser](https://babeljs.io/docs/en/babel-parser#output) 的文档里看到:
36 |
37 | 也就是,babel 基于 acorn 插件对 estree AST 做了如下扩展:
38 |
39 | * 把 Literal 替换成了 StringLiteral、NumericLiteral、 BigIntLiteral、 BooleanLiteral、 NullLiteral、 RegExpLiteral
40 | * 把 Property 替换成了 ObjectProperty 和 ObjectMethod
41 | * 把 MethodDefinition 替换成了 ClassMethod
42 | * Program 和 BlockStatement 支持了 directives 属性,也就是 'use strict' 等指令的解析,对应的 ast 是 Directive 和 DirectiveLiteral
43 | * ChainExpression 替换为了 ObjectMemberExpression 和 OptionalCallExpression
44 | * ImportExpression 替换为了 CallExpression 并且 callee 属性设置为 Import 等
45 |
46 | ## acorn 插件
47 |
48 | babel parser 基于 acorn 扩展了一些语法,那它是怎么扩展的呢?
49 |
50 | acorn 主要是一个 Parser 类,不同的方法实现了不同的逻辑,插件扩展就是继承这个 Parser,重写一些方法。
51 |
52 | acorn 的 api 如下,其中 acorn-jsx、acorn-bigint 就是 acorn 插件
53 |
54 | ```javascript
55 | const { Parser } = require("acorn")
56 |
57 | const MyParser = Parser.extend(
58 | require("acorn-jsx")(),
59 | require("acorn-bigint")
60 | )
61 | console.log(MyParser.parse("// Some bigint + JSX code"))
62 | ```
63 |
64 | 插件是一个函数,接收之前的 Parser,返回扩展以后的 Parser
65 |
66 | ```javascript
67 | module.exports = function noisyReadToken(Parser) {
68 | return class extends Parser {
69 | readToken(code) {
70 | console.log("Reading a token!")
71 | super.readToken(code)
72 | }
73 | }
74 | }
75 | ```
76 |
77 | 接下来我们写一个 acorn 插件来实现 给 javascript 一个关键字 guang,可以作为 statement 单独使用。
78 |
79 | 我们知道 parse 的过程其实就是 **`分词`** + **`组装 AST`** 这两步(一般叫词法分析和语法分析),我们只要实现这两步就可以了。
80 |
81 | ### 分词(词法分析)
82 |
83 | 我们是想增加一个关键字,acorn 有 keywords 属性,是一个正则表达式,用来做关键字拆分,所以我们重写 keywords 属性就可以。并且还要为新的关键字注册一个 token 类型。
84 |
85 | acorn Parser 的入口方法是 parse,我们要在 parse 方法里面设置 keywords。
86 |
87 | ```javascript
88 | parse(program) {
89 | var newKeywords = "break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this const class extends export import super";
90 | newKeywords += " guang";// 增加一个关键字
91 | this.keywords = new RegExp("^(?:" + newKeywords.replace(/ /g, "|") + ")$")
92 |
93 | return(super.parse(program));
94 | }
95 | ```
96 |
97 | 然后注册一个新的 token 类型来标识它
98 |
99 | ```javascript
100 | Parser.acorn.keywordTypes["guang"] = new TokenType("guang",{keyword: "guang"});
101 | ```
102 |
103 | 这样 acorn 就会在 parse 的时候分出 `guang` 这个关键字了
104 |
105 | ### 组装AST(语法分析)
106 |
107 | 在词法分析阶段,修改了正则来分出 guang 的 token 之后,就可以在语法分析的时候用它创建对应的 AST 了。
108 |
109 | acorn 是基于递归下降的思路实现的,也就是 parse 到不同类型的节点会调用不同的 parseXxx 方法,这样递归的解析。
110 |
111 | 因为我们是在 statement 里面用,那么就要重写 parseStatement 方法,在里面组装新的 statement 节点:
112 |
113 | ```javascript
114 | parseStatement(context, topLevel, exports) {
115 | var tokenType = this.type;
116 |
117 | if (tokenType == Parser.acorn.keywordTypes["guang"]) {
118 | var node = this.startNode();
119 | this.next();
120 | return this.finishNode({ value: 'guang' }, 'GuangStatement');
121 | }
122 | else {
123 | return(super.parseStatement(context, topLevel, exports));
124 | }
125 | }
126 | ```
127 |
128 | this.type 是当前处理到的 token 的类型, this.next 方式是消费这个 token,我们识别出 token 的类型为 guang 的时候,就组装成一个 AST 返回。
129 |
130 | 创建 AST 节点是用 this.startNode(),返回用 guang 这个 token 来创建的 AST 节点。
131 |
132 | 如果不是我们扩展的 token,则调用父类的 parseStatement 处理。
133 |
134 | 新的关键字的 parse 已经处理完了。但是最好还是把这段解析逻辑拆成一个新的 parseXxx 的方法,这样子类也可以继承然后覆盖它。
135 |
136 | ```javascript
137 | parseStatement(context, topLevel, exports) {
138 | var tokenType = this.type;
139 |
140 | if (tokenType == Parser.acorn.keywordTypes["guang"]) {
141 | var node = this.startNode();
142 | return this.parseGuangStatement(node);
143 | }
144 | else {
145 | return(super.parseStatement(context, topLevel, exports));
146 | }
147 | }
148 |
149 | parseGuangStatement(node) {
150 | this.next();
151 | return this.finishNode({value: 'guang'},'GuangStatement');
152 | }
153 | ```
154 |
155 | 完整代码如下:
156 |
157 | ```javascript
158 | const acorn = require("acorn");
159 |
160 | const Parser = acorn.Parser;
161 | const TokenType = acorn.TokenType;
162 |
163 | Parser.acorn.keywordTypes["guang"] = new TokenType("guang",{keyword: "guang"});
164 |
165 | function wordsRegexp(words) {
166 | return new RegExp("^(?:" + words.replace(/ /g, "|") + ")$")
167 | }
168 |
169 | var guangKeyword = function(Parser) {
170 | return class extends Parser {
171 | parse(program) {
172 | let newKeywords = "break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this const class extends export import super";
173 | newKeywords += " guang";
174 | this.keywords = new RegExp("^(?:" + newKeywords.replace(/ /g, "|") + ")$")
175 | return(super.parse(program));
176 | }
177 |
178 | parseStatement(context, topLevel, exports) {
179 | var starttype = this.type;
180 |
181 | if (starttype == Parser.acorn.keywordTypes["guang"]) {
182 | var node = this.startNode();
183 | return this.parseGuangStatement(node);
184 | }
185 | else {
186 | return(super.parseStatement(context, topLevel, exports));
187 | }
188 | }
189 |
190 | parseGuangStatement(node) {
191 | this.next();
192 | return this.finishNode({value: 'guang'},'GuangStatement');//新增加的ssh语句
193 | };
194 | }
195 | }
196 | const newParser = Parser.extend(guangKeyword);
197 | ```
198 |
199 | 执行代码:
200 |
201 | ```javascript
202 | const acorn = require("acorn");
203 | const guangKeywordPlugin = require('./guangKeywordPlugin');
204 |
205 | const Parser = acorn.Parser;
206 |
207 | const newParser = Parser.extend(guangKeywordPlugin);
208 |
209 | var program =
210 | `
211 | guang
212 | const a = 1
213 | `;
214 |
215 | const ast = newParser.parse(program);
216 | console.log(ast);
217 | ```
218 |
219 | 产出的 AST 如下:
220 |
221 | ```javascript
222 | Node {
223 | type: 'Program',
224 | start: 0,
225 | end: 27,
226 | body: [
227 | { value: 'guang', type: 'GuangStatement', end: 10 },
228 | Node {
229 | type: 'VariableDeclaration',
230 | start: 15,
231 | end: 26,
232 | declarations: [Array],
233 | kind: 'const'
234 | }
235 | ],
236 | sourceType: 'script'
237 | }
238 | ```
239 |
240 | 可以看到代码里的 guang 没有报编译错误,而是作为关键字被解析了。并且这条语句在 parse 后创建了 GuangStatement 类型的节点。
241 |
242 | :::tip
243 | 通过这个简单的例子,我们能大概理解 babel 是怎么基于 acorn 实现 typescript、jsx、flow 语法解析的了:就是通过继承和重写的方式修改了词法分析、语法分析的逻辑
244 | :::
245 |
246 | 比如 Literal 扩展了 StringLiteral、NumericLiteral 等这一点,我们就可以自己实现:
247 |
248 | ```javascript
249 | parseLiteral (...args) {
250 | const node = super.parseLiteral(...args);
251 | switch(typeof node.value) {
252 | case 'number':
253 | node.type = 'NumericLiteral';
254 | break;
255 | case 'string':
256 | node.type = 'StringLiteral';
257 | break;
258 | }
259 | return node;
260 | }
261 | ```
262 |
263 | ## 总结
264 |
265 | 基于火狐浏览器的 JS 引擎 SpiderMonkey 的 AST 标准,制定了 espree 的标准,最早的 estree 标准的实现是 esprima,但是随着 es2015 开始一年一个版本,esprima 的迭代速度逐渐跟不上了,这时候 acorn 流行起来,因为速度更快,而且支持插件扩展,于是 espree、babel parser(babylon) 等都基于 acorn 来实现各自的 parser。虽然 estree 系列的 js parser 挺多的,但也不是全部,terser、typescript 等都是用自己的 AST。
266 |
267 | babel parser 能不断地支持新的语法,就是通过修改词法分析、语法分析阶段的代码来实现的。
268 |
269 | 其实现在 babel parser 的代码里已经看不到 acorn 的依赖了,因为在 babel4 以后,babel 直接 fork 了 acorn 的代码来修改,而不是引入 acorn 包再通过插件扩展的方式。但是,原理还是一样的。
--------------------------------------------------------------------------------
/docs/babel/006_traverse_path_scope_visitor.md:
--------------------------------------------------------------------------------
1 | # traverse 的 path、scope、visitor
2 |
3 | babel 会递归遍历 AST,遍历过程中处理到不同的 AST 会调用不同的 visitor 函数来实现 transform。这其实是一种设计模式,叫做访问者模式:
4 |
5 | ## visitor 模式
6 |
7 | visitor 模式(访问者模式)是 23 种经典设计模式中的一种。visitor 模式的思想是:当被操作的对象结构比较稳定,而操作对象的逻辑经常变化的时候,通过分离逻辑和对象结构,使得他们能独立扩展。
8 |
9 | 
10 |
11 | 如图,Element 和 Visitor 分别代表对象结构和操作逻辑,两者可以独立扩展,在 Client 里面来组合两者,使用 visitor 操作 element。这就是 visitor 模式。
12 |
13 | 对应到 babel traverse 的实现,就是 AST 和 visitor 分离,在 traverse(遍历)AST 的时候,调用注册的 visitor 来对其进行处理。
14 |
15 | 
16 |
17 | 这样 AST 是独立的扩展的,visitor 是独立的扩展的,两者可以各自独立扩展单还能轻易地结合在一起。
18 |
19 |
20 | ## path 和 scope
21 |
22 | path 是记录遍历路径的 api,它记录了父子节点的引用,还有很多增删改查 AST 的 api:
23 |
24 | 
25 |
26 | 那 path 大概有哪些属性和方法呢?
27 |
28 | ## path
29 |
30 | path 大概有这些属性和方法,不需要记
31 |
32 | ```js
33 | path {
34 | // 属性:
35 | node
36 | parent
37 | parentPath
38 | scope
39 | hub
40 | container
41 | key
42 | listKey
43 |
44 | // 方法
45 | get(key)
46 | set(key, node)
47 | inList()
48 | getSibling(key)
49 | getNextSibling()
50 | getPrevSibling()
51 | getAllPrevSiblings()
52 | getAllNextSiblings()
53 | isXxx(opts)
54 | assertXxx(opts)
55 | find(callback)
56 | findParent(callback)
57 |
58 | insertBefore(nodes)
59 | insertAfter(nodes)
60 | replaceWith(replacement)
61 | replaceWithMultiple(nodes)
62 | replaceWithSourceString(replacement)
63 | remove()
64 |
65 | traverse(visitor, state)
66 | skip()
67 | stop()
68 | }
69 | ```
70 |
71 | 它们各自的含义:
72 |
73 | ```js
74 | path.node 当前 AST 节点
75 | path.parent 父 AST 节点
76 | path.parentPath 父 AST 节点的 path
77 | path.scope 作用域
78 | path.hub 可以通过 path.hub.file 拿到最外层 File 对象, path.hub.getScope 拿到最外层作用域,path.hub.getCode 拿到源码字符串
79 | path.container 当前 AST 节点所在的父节点属性的属性值
80 | path.key 当前 AST 节点所在父节点属性的属性名或所在数组的下标
81 | path.listkey 当前 AST 节点所在父节点属性的属性值为数组时 listkey 为该属性名,否则为 undefined
82 | ```
83 |
84 | ### container、listkey、key
85 |
86 | 这几个属性不太常用,简单介绍一下。
87 |
88 | 因为 AST 节点要挂在父 AST 节点的某个属性上,那个属性的属性值就是这个 AST 节点的 **container**。
89 |
90 | 比如 CallExpression 有 callee 和 arguments 属性,那么对于 callee 的 AST 节点来说,callee 的属性值就是它的 container,而 callee 就是它的 key。
91 |
92 | 因为不是一个列表,所以 listkey 是 undefined。
93 |
94 | 
95 |
96 | 而 BlockStatement 有 body 属性,是一个数组,对于数组中的每一个 AST 来说,这个数组就是它们的 container,而 listKey 是 body,key 则是下标。
97 |
98 | 
99 |
100 | ### path 的方法
101 |
102 | path 有如下方法,同样也不需要记:
103 |
104 | ```js
105 | get(key) 获取某个属性的 path
106 | set(key, node) 设置某个属性的值
107 | getSibling(key) 获取某个下标的兄弟节点
108 | getNextSibling() 获取下一个兄弟节点
109 | getPrevSibling() 获取上一个兄弟节点
110 | getAllPrevSiblings() 获取之前的所有兄弟节点
111 | getAllNextSiblings() 获取之后的所有兄弟节点
112 | find(callback) 从当前节点到根节点来查找节点(包括当前节点),调用 callback(传入 path)来决定是否终止查找
113 | findParent(callback) 从当前节点到根节点来查找节点(不包括当前节点),调用 callback(传入 path)来决定是否终止查找
114 | inList() 判断节点是否在数组中,如果 container 为数组,也就是有 listkey 的时候,返回 true
115 | isXxx(opts) 判断当前节点是否是某个类型,可以传入属性和属性值进一步判断,比如path.isIdentifier({name: 'a'})
116 | assertXxx(opts) 同 isXxx,但是不返回布尔值,而是抛出异常
117 | insertBefore(nodes) 在之前插入节点,可以是单个节点或者节点数组
118 | insertAfter(nodes) 在之后插入节点,可以是单个节点或者节点数组
119 | replaceWith(replacement) 用某个节点替换当前节点
120 | replaceWithMultiple(nodes) 用多个节点替换当前节点
121 | replaceWithSourceString(replacement) 解析源码成 AST,然后替换当前节点
122 | remove() 删除当前节点
123 | traverse(visitor, state) 遍历当前节点的子节点,传入 visitor 和 state(state 是不同节点间传递数据的方式)
124 | skip() 跳过当前节点的子节点的遍历
125 | stop() 结束所有遍历
126 | ```
127 |
128 | ### 作用域 path.scope
129 |
130 | scope 是作用域信息,javascript 中能生成作用域的就是模块、函数、块等,而且作用域之间会形成嵌套关系,也就是作用域链。babel 在遍历的过程中会生成作用域链保存在 path.scope 中。
131 |
132 | 属性和方法大概有这些
133 |
134 | ```js
135 | path.scope {
136 | bindings
137 | block
138 | parent
139 | parentBlock
140 | path
141 | references
142 |
143 | dump()
144 | getAllBindings()
145 | getBinding(name)
146 | hasBinding(name)
147 | getOwnBinding(name)
148 | parentHasBinding(name)
149 | removeBinding(name)
150 | moveBindingTo(name, scope)
151 | generateUid(name)
152 | }
153 | ```
154 |
155 | 各自的含义:
156 |
157 | ```js
158 | scope.bindings 当前作用域内声明的所有变量
159 | scope.block 生成作用域的 block,详见下文
160 | scope.path 生成作用域的节点对应的 path
161 | scope.references 所有 binding 的引用对应的 path,详见下文
162 | scope.dump() 打印作用域链的所有 binding 到控制台
163 | scope.parentBlock 父级作用域的 block
164 | getAllBindings() 从当前作用域到根作用域的所有 binding 的合并
165 | getBinding(name) 查找某个 binding,从当前作用域一直查找到根作用域
166 | getOwnBinding(name) 从当前作用域查找 binding
167 | parentHasBinding(name, noGlobals) 查找某个 binding,从父作用域查到根作用域,不包括当前作用域。可以通过 noGlobals 参数指定是否算上全局变量(比如console,不需要声明就可用),默认是 false
168 | removeBinding(name) 删除某个 binding
169 | hasBinding(name, noGlobals) 从当前作用域查找 binding,可以指定是否算上全局变量,默认是 false
170 | moveBindingTo(name, scope) 把当前作用域中的某个 binding 移动到其他作用域
171 | generateUid(name) 生成作用域内唯一的名字,根据 name 添加下划线,比如 name 为 a,会尝试生成 _a,如果被占用就会生成 __a,直到生成没有被使用的名字。
172 | ```
173 |
174 | ### scope.block
175 |
176 | 能形成 scope 的有这些节点,这些节点也叫 block 节点。
177 |
178 | ```js
179 | export type Scopable =
180 | | BlockStatement
181 | | CatchClause
182 | | DoWhileStatement
183 | | ForInStatement
184 | | ForStatement
185 | | FunctionDeclaration
186 | | FunctionExpression
187 | | Program
188 | | ObjectMethod
189 | | SwitchStatement
190 | | WhileStatement
191 | | ArrowFunctionExpression
192 | | ClassExpression
193 | | ClassDeclaration
194 | | ForOfStatement
195 | | ClassMethod
196 | | ClassPrivateMethod
197 | | StaticBlock
198 | | TSModuleBlock;
199 | ```
200 |
201 | 我们可以通过 path.scope.block 来拿到所在的块对应的节点,通过 path.scope.parentBlock 拿到父作用域对应的块节点。
202 |
203 | 一般情况下我们不需要拿到生成作用域的块节点,只需要通过 path.scope 拿到作用域的信息,通过 path.scope.parent 拿到父作用域的信息。
204 |
205 | ### scope.bindings、scope.references(重点)
206 |
207 | 作用域中保存的是声明的变量和对应的值,**每一个声明叫做一个binding**。
208 |
209 | 比如这样一段代码
210 |
211 | ```js
212 | const a = 1;
213 | ```
214 |
215 | 它的 path.scope.bindings 是这样的
216 |
217 | ```js
218 | bindings: {
219 | a: {
220 | constant: true,
221 | constantViolations: [],
222 | identifier: { type: 'Identifier', ... }
223 | kind:'const',
224 | path: { node, ... }
225 | referenced: false
226 | referencePaths: [],
227 | references: 0,
228 | scope: ...
229 | }
230 | }
231 | ```
232 |
233 | 因为我们在当前 scope 中声明了 a 这个变量,所以 bindings 中有 a 的 binding,每一个 binding 都有 `kind`,这代表绑定的类型:
234 |
235 | * var、let、const 分别代表 var、let、const 形式声明的变量
236 | * param 代表参数的声明
237 | * module 代表 import 的变量的声明
238 |
239 | binding 有多种 `kind`,代表变量是用不同的方式声明的。
240 |
241 | `binding.identifier` 和 `binding.path`,分别代表标识符、整个声明语句。
242 |
243 | 声明之后的变量会被引用和修改,`binding.referenced` 代表声明的变量是否被引用,`binding.constant` 代表变量是否被修改过。如果被引用了,就可以通过 `binding.referencePaths` 拿到所有引用的语句的 `path`。如果被修改了,可以通过 `binding.constViolations` 拿到所有修改的语句的 path。
244 |
245 | :::tip
246 | path 的 api 还是比较多的,这也是 babel 最强大的地方。主要是操作当前节点、当前节点的父节点、兄弟节点,作用域,以及增删改的方法。
247 | :::
248 |
249 | ## state
250 |
251 | `state` 是遍历过程中 AST 节点之间传递数据的方式。插件的 visitor 中,第一个参数是 path,第二个参数就是 state。
252 |
253 | 插件可以从 state 中拿到 opts,也就是插件的配置项,也可以拿到 file 对象,file 中有一些文件级别的信息,这个也可以从 path.hub.file 中拿。
254 |
255 | ```js
256 | state {
257 | file
258 | opts
259 | }
260 | ```
261 |
262 | 可以在遍历的过程中在 state 中存一些状态信息,用于后续的 AST 处理。
263 |
264 | ## AST 的别名
265 |
266 | 遍历的时候要指定 visitor 处理的 AST,有的时候需要对多个节点做同样的处理,babel 支持指定多个 AST 类型,也可以通过别名指定一系列类型。
267 |
268 | ```js
269 | // 单个 AST 类型
270 | FunctionDeclaration(path, state) {},
271 | // 多个 AST 类型
272 | 'FunctionDeclaration|VariableDeclaration'(path, state) {}
273 | // AST 类型别名
274 | Declaration(){}
275 | ```
276 |
277 | 可以在[文档](https://babeljs.io/docs/en/babel-types)中查到某个 AST 类型的别名是啥,某个别名都包含哪些 AST 类型可以在[babel-types的类型定义](https://github.com/babel/babel/blob/main/packages/babel-types/src/ast-types/generated/index.ts#L2489-L2535)处查。
278 |
279 | 可以把 @babel/types 源码下载下来看,类型定义在 src/ast-types/generated 目录下,这样可以利用 ide 的功能方便的查看每种 alias 的具体 AST 类型。
280 |
281 | 
282 |
283 | 所有的 AST 相关的信息都可以在[babel-types](https://github.com/babel/babel/blob/main/packages/babel-types/src/definitions/core.ts)里查看,每一个 AST 节点怎么创建、怎么校验、怎么遍历,其实都与 AST 的结构有关系,这些都在 babel-types 里面定义。
284 |
285 | 比如 if 就定义了有哪些属性可以遍历、别名是什么,每一个属性怎么校验,然后会根据这些规则生成xxx,isXxx,assertXxx等api用于创建、判断AST节点。
286 |
287 | 
288 |
289 |
290 | ## 总结
291 |
292 | * path:游走 node 节点;
293 | * scope:作用域相关,比如引用:references,作用域内相关的定义和引用;
294 | * state: 遍历游走时的穿透上下文。包括 opts & file 信息;
295 | * AST 和 visitor 是结构和逻辑相分离;
296 | * visitor 可扩展的节点的遍历处理 方法;
--------------------------------------------------------------------------------
/docs/babel/007_generator_sourcemap.md:
--------------------------------------------------------------------------------
1 | # Generator 和 sourceMap
2 |
3 | ## generate
4 |
5 | generate 是把 AST 打印成字符串,是一个从根节点递归打印的过程,对不同的 AST 节点做不同的处理,在这个过程中把抽象语法树中省略掉的一些分隔符重新加回来。
6 |
7 |
8 | 比如 while 语句 WhileStatement 就是先打印 while,然后打印一个空格和 '(',然后打印 node.test 属性的节点,然后打印 ')',之后打印 block 部分
9 |
10 | [Generators Statements](https://github.com/babel/babel/blob/main/packages/babel-generator/src/generators/statements.ts)
11 |
12 | ```js
13 | export function WhileStatement(this: Printer, node: t.WhileStatement) {
14 | this.word("while");
15 | this.space();
16 | this.token("(");
17 | this.print(node.test, node);
18 | this.token(")");
19 | this.printBlock(node);
20 | }
21 |
22 | ```
23 |
24 | 比如条件表达式 ConditionalExpression 就是分别打印 node.test、node.consequent、node.alternate 属性,中间插入 ? : 和空格。
25 |
26 | [Generators Statements](https://github.com/babel/babel/blob/main/packages/babel-generator/src/generators/expressions.ts)
27 |
28 | ```js
29 | export function ConditionalExpression(
30 | this: Printer,
31 | node: t.ConditionalExpression,
32 | ) {
33 | this.print(node.test, node);
34 | this.space();
35 | this.token("?");
36 | this.space();
37 | this.print(node.consequent, node);
38 | this.space();
39 | this.token(":");
40 | this.space();
41 | this.print(node.alternate, node);
42 | }
43 | ```
44 |
45 | 通过这样的方式递归打印整个 AST,就可以生成目标代码。
46 |
47 | ## sourcemap
48 |
49 | ### sourcemap作用
50 |
51 | babel 对源码进行了修改,生成的目标代码可能改动很大,如果直接调试目标代码,那么可能很难对应到源码里。所以需要一种自动关联源码的方式,就是 sourcemap。
52 |
53 | 我们平时用 sourcemap 主要用两个目的:
54 |
55 | #### 调试代码时定位到源码
56 |
57 | chrome、firefox 等浏览器支持在文件末尾加上[一行注释](https://firefox-source-docs.mozilla.org/devtools-user/debugger/how_to/use_a_source_map/index.html)
58 |
59 | ```js
60 | //# sourceMappingURL=http://example.com/path/to/your/sourcemap.map
61 | ```
62 |
63 | 可以通过 url 的方式或者转成 base64 内联的方式来关联 sourcemap。调试工具(浏览器、vscode 等会自动解析 sourcemap,关联到源码。这样打断点、错误堆栈等都会对应到相应源码。
64 |
65 | #### 线上报错定位到源码
66 |
67 | 开发时会使用 sourcemap 来调试,但是生产可不会,要是把 sourcemap 传到生产算是大事故了。但是线上报错的时候确实也需要定位到源码,这种情况一般都是单独上传 sourcemap 到错误收集平台。
68 |
69 | 比如 sentry 就提供了一个 [@sentry/webpack-plugin](https://www.npmjs.com/package/@sentry/webpack-plugin) 支持在打包完成后把 sourcemap 自动上传到 sentry 后台,然后把本地 sourcemap 删掉。还提供了 [@sentry/cli](https://www.npmjs.com/package/@sentry/cli) 让用户可以手动上传。
70 |
71 | 平时我们至少在这两个场景(开发时调试源码,生产时定位错误)下会用到 sourcemap。
72 |
73 | ### sourcemap格式
74 |
75 | ```js
76 | {
77 | version : 3,
78 | file: "out.js",
79 | sourceRoot : "",
80 | sources: ["foo.js", "bar.js"],
81 | names: ["src", "maps", "are", "fun"],
82 | mappings: "AAgBC,SAAQ,CAAEA"
83 | }
84 | ```
85 |
86 | 比如上面就是一个 sourcemap 文件,对应字段的含义如下:
87 |
88 | * version:source map的版本,目前为3。
89 |
90 | * file:转换后的文件名。
91 |
92 | * sourceRoot:转换前的文件所在的目录。如果与转换前的文件在同一目录,该项为空。
93 |
94 | * sources:转换前的文件。该项是一个数组,因为可能是多个源文件合并成一个目标文件。
95 |
96 | * names:转换前的所有变量名和属性名,把所有变量名提取出来,下面的 mapping 直接使用下标引用,可以减少体积。
97 |
98 | * mappings:转换前代码和转换后代码的映射关系的集合,用分号代表一行,每行的 mapping 用逗号分隔。
99 |
100 | 每一个分号 `;` 表示一行,多个空行就是多个 `;`,mapping 通过 `,` 分割。
101 |
102 | mapping有五位:
103 |
104 | * 第一位是目标代码中的列数
105 | * 第二位是源码所在的文件名
106 | * 第三位是源码对应的行数
107 | * 第四位是源码对应的列数
108 | * 第五位是源码对应的 names,不一定有
109 |
110 | 每一位是通过 VLQ 编码的,一个字符就能表示行列数,[原理探究](https://juejin.cn/post/6844903689610592269)
111 |
112 | sourcemap 通过 `names` 和 `;` 的设计省略掉了一些变量名和行数所占的空间,又通过 VLQ 编码使得一个字符就可以表示行列数等信息。通过不大的空间占用完成了源码到目标代码的映射。
113 |
114 | 那么 sourcemap 的源码和目标代码的行列数是怎么来的呢?
115 |
116 | 其实我们在 parse 的时候就在 AST 节点中保存了 loc 属性,这就是源码中的行列号,在后面 transform 的过程中,并不会去修改它,所以转换完以后节点中仍然保留有源码中的行列号信息,在 generate 打印成目标代码的时候会计算出新的行列号,这样两者关联就可以生成 sourcemap。
117 |
118 | 
119 |
120 | 具体生成 sourcemap 的过程是用 mozilla 维护的 [source-map](https://www.npmjs.com/package/source-map) 这个包,其他工具做 sourcemap 的解析和生成也是基于这个包。
--------------------------------------------------------------------------------
/docs/babel/008_babel_plugin_preset.md:
--------------------------------------------------------------------------------
1 | # Babel 插件和 preset
2 |
3 | 本文主要介绍 `babel` 插件的格式和 `preset`
4 |
5 | ## plugin 的使用
6 |
7 |
--------------------------------------------------------------------------------
/docs/babel/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | home: true
3 | ---
4 |
5 | | 教程 | 案例 |
6 | | -- | -- |
7 | | [Babel 7](/babel/babel7.html) | [插入函数调用参数](/babel/004_demo_insert_params.html) |
8 | | [Babel介绍](/babel/001_babel_introduce.html) | 1 |
9 | | [Babel AST](/babel/002_babel_ast.html) | 1 |
10 | | [Babel API](/babel/003_babel_api.html) | 1 |
11 | | [Babel Parser](/babel/005_history_of_js_parser.html) | 1 |
12 | | [Babel Traverse: Path Scope visitor](/babel/006_traverse_path_scope_visitor.html) | 1 |
13 | | [Generator和SourceMap](/babel/007_generator_sourcemap.html) | 1 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/docs/babel/babel7.md:
--------------------------------------------------------------------------------
1 | # Babel 7
2 |
3 | [Babel文档](https://www.babeljs.cn/docs)
4 |
5 | ## Babel 是什么?
6 |
7 | Babel 是一个工具链,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。下面列出的是 Babel 能为你做的事情:
8 |
9 | * 语法转换
10 | * 通过 Polyfill 方式在目标环境中添加缺失的特性 (通过 [@babel/polyfill](https://www.babeljs.cn/docs/babel-polyfill) 模块)
11 | * 源码转换 (codemods)
12 |
13 | 其目的是:
14 |
15 | * ES2015+ 的语法转化(如箭头函数转成普通函数)
16 | * ES2015+ 新增的方法转化(如数组新增的includes方法转化兼容低版本游览器)
17 |
18 | ## @babel/cli 与 babel-cli
19 |
20 | babel-cli 为 `babel6` 使用的包,@babel/cli 为 `babel7` 以后使用的包. 除此之外还有 `@babel/core` 等等.
21 |
22 | 换言之,有@开头是babel7 反之为babel6.
23 |
24 | ## 插件
25 |
26 | Babel 是一个编译器(输入源码 => 输出编译后的代码)。就像其他编译器一样,编译过程分为三个阶段:解析、转换和打印输出。
27 |
28 | 现在,Babel 虽然开箱即用,但是什么动作都不做。它基本上类似于 `const babel = code => code`; ,将代码解析之后再输出同样的代码。如果想要 Babel 做一些实际的工作,就需要为其添加插件。
29 |
30 | ## 预设(Presets)
31 |
32 | `babel`提供了一个叫做 `preset` 的概念,其实就是 `插件包`,意味着babel会预先替我们做好了一系列的插件包.
33 |
34 | ### 插件包
35 |
36 | 常用的插件包
37 |
38 | * @babel/preset-env
39 | * @babel/preset-flow
40 | * @babel/preset-react
41 | * @babel/preset-typescript
42 |
43 | ## babel 配置
44 |
45 | * @babel/cli
46 |
47 | @babel/cli 是babel提供的命令行工具,主要是提供babel这个命令。 官网推荐安装在项目中,而不是安装在全局环境,因为每个项目用的babel的版本不一样。可以单独管理和升级。更主要是为了方便以后项目的迁移。
48 |
49 | * @babel/core
50 |
51 | Babel 的核心功能包含在 @babel/core 模块中。必须安装.
52 |
53 | * @babel/plugin-transform-arrow-functions
54 |
55 | 编译 ES2015 箭头函数到 ES5
56 |
57 | ```js
58 | // .babelrc
59 | {
60 | "plugins": [
61 | "@babel/plugin-transform-arrow-functions"
62 | ]
63 | }
64 | ```
65 |
66 | * @babel/preset-env
67 |
68 | 预设 preset 插件包,包含 `@babel/plugin-transform-arrow-functions`. 具体说明:[preset-env](https://babeljs.io/docs/en/babel-preset-env)
69 |
70 | 预设包并不能完全转化所有es6新增语法,比如 `includes`. 这时可以使用 `@babel/polyfill`
71 |
72 | 或者配置 `useBuiltIns` 为 `usage`,就只会包含代码需要的 polyfill. 同时安装 `core-js@3` 库.
73 |
74 | ```js
75 | {
76 | "presets": [
77 | [
78 | "@babel/preset-env",
79 | {
80 | "targets": {
81 | "browsers": [
82 | "> 1%",
83 | "last 2 versions"
84 | ]
85 | },
86 | "useBuiltIns": "usage",
87 | "corejs": 3
88 | }
89 | ]
90 | ]
91 | }
92 | ```
93 |
94 | * @babel/polyfill
95 |
96 | `polyfill` 我们又称垫片,见名知意,所谓垫片也就是垫平不同浏览器或者不同环境下的差异,因为有的环境支持这个函数,有的环境不支持这种函数,解决的是有与没有的问题,这个是靠单纯的 `@babel/preset-env` 不能解决的,因为 `@babel/preset-env` 解决的是将高版本写法转化成低版本写法的问题,因为不同环境下低版本的写法有可能不同而已。
97 |
98 | ```js
99 | // app.js
100 | import '@babel/polyfill';
101 | ```
102 |
103 | * @babel/plugin-transform-runtime
104 |
105 | 所有帮助程序都将引用模块 `@babel/runtime`,这样就可以避免编译后的代码中出现重复的帮助程序,有效减少包体积. `@babel/plugin-transform-runtime` 需要和 `@babel/runtime` 配合使用.
106 |
107 | `babel/plugin-transform-runtime` 通常仅在开发时使用,但是运行时最终代码需要依赖 `@babel/runtime`,所以 `@babel/runtime` 必须要作为生产依赖被安装,
108 |
109 | ```bash
110 | $ npm install --save-dev @babel/plugin-transform-runtime
111 | $ npm install --save @babel/runtime
112 | ```
113 |
114 | ```js
115 | {
116 | "presets": [
117 | [
118 | "@babel/preset-env",
119 | {
120 | "useBuiltIns": "usage",
121 | "corejs": 3
122 | }
123 | ]
124 | ],
125 | "plugins": [
126 | [
127 | "@babel/plugin-transform-runtime"
128 | ]
129 | ]
130 | }
131 | ```
--------------------------------------------------------------------------------
/docs/browser/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | home: true
3 | ---
4 |
5 | [从输入URL到页面展示](/browser/input-url-to-page.html)
6 |
7 | [浏览器缓存机制](/browser/browser-cache.html)
8 |
9 | [浏览器安全](/browser/browser-security.html)
--------------------------------------------------------------------------------
/docs/browser/browser-cache.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebarDepth: 3
3 | ---
4 |
5 | # 浏览器缓存机制
6 |
7 | 
8 |
9 |
10 | ## 缓存位置
11 |
12 | 从缓存位置上来说分为四种,并且各自有优先级,当依次查找缓存且都没有命中的时候,才会去请求网络。
13 |
14 | * Service Worker
15 | * Memory Cache
16 | * Disk Cache
17 | * Push Cache
18 |
19 | ### 1.Service Worker
20 |
21 | Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。**Service Worker 的缓存与浏览器其他内建的缓存机制不同,它可以让我们自由控制缓存哪些文件、如何匹配缓存、如何读取缓存,并且缓存是持续性的。**
22 |
23 | Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。
24 |
25 | 当 Service Worker 没有命中缓存的时候,我们需要去调用 fetch 函数获取数据。也就是说,如果我们没有在 Service Worker 命中缓存的话,会根据缓存查找优先级去查找数据。但是不管我们是从 Memory Cache 中还是从网络请求中获取的数据,浏览器都会显示我们是从 Service Worker 中获取的内容。
26 |
27 | ### 2.Memory Cache
28 |
29 | Memory Cache 也就是内存中的缓存,主要包含的是当前中页面中已经抓取到的资源,例如页面上已经下载的样式、脚本、图片等。读取内存中的数据肯定比磁盘快,内存缓存虽然读取高效,可是缓存持续性很短,会随着进程的释放而释放。 **一旦我们关闭 Tab 页面,内存中的缓存也就被释放了。**
30 |
31 | **那么既然内存缓存这么高效,我们是不是能让数据都存放在内存中呢?**
32 | 这是不可能的。计算机中的内存一定比硬盘容量小得多,操作系统需要精打细算内存的使用,所以能让我们使用的内存必然不多。
33 |
34 | 当我们访问过页面以后,再次刷新页面,可以发现很多数据都来自于内存缓存
35 | 。
36 |
37 | 
38 |
39 | 内存缓存中有一块重要的缓存资源是preloader相关指令(例如``)下载的资源。总所周知preloader的相关指令已经是页面优化的常见手段之一,它可以一边解析js/css文件,一边网络请求下一个资源。
40 |
41 |
42 | 需要注意的事情是,**内存缓存在缓存资源时并不关心返回资源的HTTP缓存头Cache-Control是什么值,同时资源的匹配也并非仅仅是对URL做匹配,还可能会对Content-Type,CORS等其他特征做校验。**
43 |
44 | ### 3.Disk Cache
45 |
46 | Disk Cache 也就是存储在硬盘中的缓存,读取速度慢点,但是什么都能存储到磁盘中,**比之 Memory Cache 胜在容量和存储时效性上。**
47 |
48 | 在所有浏览器缓存中,Disk Cache 覆盖面基本是最大的。它会根据 HTTP Herder 中的字段判断哪些资源需要缓存,哪些资源可以不请求直接使用,哪些资源已经过期需要重新请求。并且即使在跨站点的情况下,相同地址的资源一旦被硬盘缓存下来,就不会再次去请求数据。绝大部分的缓存都来自 Disk Cache,关于 HTTP 的协议头中的缓存字段,我们会在下文进行详细介绍。
49 |
50 | 浏览器会把哪些文件丢进内存中?哪些丢进硬盘中?
51 | 关于这点,网上说法不一,不过以下观点比较靠得住:
52 |
53 | * 对于大文件来说,大概率是不存储在内存中的,反之优先
54 | * 当前系统内存使用率高的话,文件优先存储进硬盘
55 |
56 | ### 4.Push Cache
57 |
58 | Push Cache(推送缓存)是 HTTP/2 中的内容,当以上三种缓存都没有命中时,它才会被使用。**它只在会话(Session)中存在,一旦会话结束就被释放,并且缓存时间也很短暂,**在Chrome浏览器中只有5分钟左右,同时它也并非严格执行HTTP头中的缓存指令。
59 |
60 | Push Cache 在国内能够查到的资料很少,也是因为 HTTP/2 在国内不够普及。这里推荐阅读Jake Archibald的 HTTP/2 push is tougher than I thought 这篇文章,文章中的几个结论
61 |
62 | * 所有的资源都能被推送,并且能够被缓存,但是 Edge 和 Safari 浏览器支持相对比较差
63 | * 可以推送 no-cache 和 no-store 的资源
64 | * 一旦连接被关闭,Push Cache 就被释放
65 | * 多个页面可以使用同一个HTTP/2的连接,也就可以使用同一个Push Cache。这主要还是依赖浏览器的实现而定,出于对性能的考虑,有的浏览器会对相同域名但不同的tab标签使用同一个HTTP连接。
66 | * Push Cache 中的缓存只能被使用一次
67 | * 浏览器可以拒绝接受已经存在的资源推送
68 | * 你可以给其他域名推送资源
69 |
70 | 如果以上四种缓存都没有命中的话,那么只能发起请求来获取资源了。
71 |
72 | 那么为了性能上的考虑,大部分的接口都应该选择好缓存策略,**通常浏览器缓存策略分为两种:强缓存和协商缓存,并且缓存策略都是通过设置 HTTP Header 来实现的。**
73 |
74 | ## 缓存过程分析
75 |
76 | 浏览器与服务器通信的方式为应答模式,即是:浏览器发起HTTP请求 – 服务器响应该请求,**那么浏览器怎么确定一个资源该不该缓存,如何去缓存呢?**浏览器第一次向服务器发起该请求后拿到请求结果后,将请求结果和缓存标识存入浏览器缓存,**浏览器对于缓存的处理是根据第一次请求资源时返回的响应头来确定的。**具体过程如下图:
77 |
78 | 
79 |
80 | 由上图我们可以知道:
81 |
82 | * 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
83 | * 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
84 |
85 | 以上两点结论就是浏览器缓存机制的关键,它确保了每个请求的缓存存入与读取,只要我们再理解浏览器缓存的使用规则,那么所有的问题就迎刃而解了,本文也将围绕着这点进行详细分析。为了方便大家理解,这里我们根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分,分别是强缓存和协商缓存。
86 |
87 | ## 强缓存
88 |
89 | 强缓存:不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。
90 |
91 | ### Expires
92 |
93 | **缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。** 也就是说,Expires=max-age + 请求时间,需要和Last-modified结合使用。Expires是Web服务器响应消息头字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求。
94 |
95 | **Expires 是 HTTP/1 的产物,受限于本地时间,如果修改了本地时间,可能会造成缓存失效。** Expires: Wed, 22 Oct 2018 08:41:00 GMT表示资源会在 Wed, 22 Oct 2018 08:41:00 GMT 后过期,需要再次请求。
96 |
97 | ### Cache-Control
98 |
99 | 在HTTP/1.1中,Cache-Control是最重要的规则,主要用于控制网页缓存。比如当Cache-Control:max-age=300时,则代表在这个请求正确返回时间(浏览器也会记录下来)的5分钟内再次加载资源,就会命中强缓存。
100 |
101 | Cache-Control 可以在请求头或者响应头中设置,并且可以组合使用多种指令:
102 |
103 | 
104 |
105 | public:所有内容都将被缓存(客户端和代理服务器都可缓存)。具体来说响应可被任何中间节点缓存,如 Browser <-- proxy1 <-- proxy2 <-- Server,中间的proxy可以缓存资源,比如下次再请求同一资源proxy1直接把自己缓存的东西给 Browser 而不再向proxy2要。
106 |
107 | private:所有内容只有客户端可以缓存,Cache-Control的默认取值。具体来说,表示中间节点不允许缓存,对于Browser <-- proxy1 <-- proxy2 <-- Server,proxy 会老老实实把Server 返回的数据发送给proxy1,自己不缓存任何数据。当下次Browser再次请求时proxy会做好请求转发而不是自作主张给自己缓存的数据。
108 |
109 | no-cache:客户端缓存内容,是否使用缓存则需要经过协商缓存来验证决定。表示不使用 Cache-Control的缓存控制方式做前置验证,而是使用 Etag 或者Last-Modified字段来控制缓存。需要注意的是,no-cache这个名字有一点误导。设置了no-cache之后,并不是说浏览器就不再缓存数据,只是浏览器在使用缓存数据时,需要先确认一下数据是否还跟服务器保持一致。
110 |
111 | no-store:所有内容都不会被缓存,即不使用强制缓存,也不使用协商缓存
112 |
113 | max-age:max-age=xxx (xxx is numeric)表示缓存内容将在xxx秒后失效
114 |
115 | s-maxage(单位为s):同max-age作用一样,只在代理服务器中生效(比如CDN缓存)。比如当s-maxage=60时,在这60秒中,即使更新了CDN的内容,浏览器也不会进行请求。max-age用于普通缓存,而s-maxage用于代理缓存。s-maxage的优先级高于max-age。如果存在s-maxage,则会覆盖掉max-age和Expires header。
116 |
117 | max-stale:能容忍的最大过期时间。max-stale指令标示了客户端愿意接收一个已经过期了的响应。如果指定了max-stale的值,则最大容忍时间为对应的秒数。如果没有指定,那么说明浏览器愿意接收任何age的响应(age表示响应由源站生成或确认的时间与当前时间的差值)。
118 |
119 | min-fresh:能够容忍的最小新鲜度。min-fresh标示了客户端不愿意接受新鲜度不多于当前的age加上min-fresh设定的时间之和的响应。
120 |
121 | 
122 |
123 | 从图中我们可以看到,我们可以将多个指令配合起来一起使用,达到多个目的。比如说我们希望资源能被缓存下来,并且是客户端和代理服务器都能缓存,还能设置缓存失效时间等等。
124 |
125 | ### Expires和Cache-Control两者对比
126 |
127 | 其实这两者差别不大,区别就在于 Expires 是http1.0的产物,Cache-Control是http1.1的产物,**两者同时存在的话,Cache-Control优先级高于Expires**;在某些不支持HTTP1.1的环境下,Expires就会发挥用处。所以Expires其实是过时的产物,现阶段它的存在只是一种兼容性的写法。
128 | 强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新,这可能会导致加载文件不是服务器端最新的内容,那我们如何获知服务器端内容是否已经发生了更新呢?此时我们需要用到协商缓存策略。
129 |
130 | ## 协商缓存
131 |
132 | 协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程,主要有以下两种情况:
133 |
134 | * 协商缓存生效,返回304和Not Modified
135 |
136 | 
137 |
138 | * 协商缓存失效,返回200和请求结果
139 |
140 | 
141 |
142 | 协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag 。
143 |
144 | ### 1.Last-Modified和If-Modified-Since
145 |
146 | 浏览器在第一次访问资源时,服务器返回资源的同时,在response header中添加 Last-Modified的header,值是这个资源在服务器上的最后修改时间,浏览器接收后缓存文件和header;
147 |
148 | ```js
149 | Last-Modified: Fri, 22 Jul 2016 01:47:00 GMT
150 | ```
151 |
152 | 浏览器下一次请求这个资源,浏览器检测到有 Last-Modified这个header,于是添加If-Modified-Since这个header,值就是Last-Modified中的值;服务器再次收到这个资源请求,会根据 If-Modified-Since 中的值与服务器中这个资源的最后修改时间对比,如果没有变化,返回304和空的响应体,直接从缓存读取,如果If-Modified-Since的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是返回新的资源文件和200
153 |
154 | 
155 |
156 | #### 但是 Last-Modified 存在一些弊端:
157 |
158 | * 如果本地打开缓存文件,即使没有对文件进行修改,但还是会造成 Last-Modified 被修改,服务端不能命中缓存导致发送相同的资源
159 | * 因为 Last-Modified 只能以秒计时,如果在不可感知的时间内修改完成文件,那么服务端会认为资源还是命中了,不会返回正确的资源
160 |
161 | 既然根据文件修改时间来决定是否缓存尚有不足,能否可以直接根据文件内容是否修改来决定缓存策略?所以在 HTTP / 1.1 出现了 ETag 和If-None-Match
162 |
163 | ### 2.ETag和If-None-Match
164 |
165 | **Etag是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag就会重新生成**。浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到request header里的If-None-Match里,服务器只需要比较客户端传来的If-None-Match跟自己服务器上该资源的ETag是否一致,就能很好地判断资源相对客户端而言是否被修改过了。如果服务器发现ETag匹配不上,那么直接以常规GET 200回包形式将新的资源(当然也包括了新的ETag)发给客户端;如果ETag是一致的,则直接返回304知会客户端直接使用本地缓存即可。
166 |
167 | 
168 |
169 | ### 3.两者之间对比:
170 |
171 | * 首先在精确度上,Etag要优于Last-Modified。
172 |
173 | Last-Modified的时间单位是秒,如果某个文件在1秒内改变了多次,那么他们的Last-Modified其实并没有体现出来修改,但是Etag每次都会改变确保了精度;如果是负载均衡的服务器,各个服务器生成的Last-Modified也有可能不一致。
174 |
175 | * 第二在性能上,Etag要逊于Last-Modified,毕竟Last-Modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash值
176 |
177 | * 第三在优先级上,服务器校验优先考虑Etag
178 |
179 | ## 缓存机制
180 |
181 | 强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回304,继续使用缓存。具体流程图如下
182 |
183 | 
184 |
185 | 看到这里,不知道你是否存在这样一个疑问: **如果什么缓存策略都没设置,那么浏览器会怎么处理?**
186 |
187 | 对于这种情况,浏览器会采用一个启发式的算法,通常会取响应头中的 Date 减去 Last-Modified 值的 10% 作为缓存时间。
188 |
189 | ## 实际场景应用缓存策略
190 |
191 | ### 1.频繁变动的资源
192 |
193 | **Cache-Control: no-cache**
194 |
195 | 对于频繁变动的资源,首先需要使用 `Cache-Control: no-cache` 使浏览器每次都请求服务器,然后配合 ETag 或者 Last-Modified 来验证资源是否有效。这样的做法虽然不能节省请求数量,但是能显著减少响应数据大小。
196 |
197 | ## 2.不常变化的资源
198 |
199 | **Cache-Control: max-age=31536000**
200 |
201 | 通常在处理这类资源时,给它们的 Cache-Control 配置一个很大的 max-age=31536000 (一年),这样浏览器之后请求相同的 URL 会命中强制缓存。而为了解决更新的问题,就需要在文件名(或者路径)中添加 hash, 版本号等动态字符,之后更改动态字符,从而达到更改引用 URL 的目的,让之前的强制缓存失效 (其实并未立即失效,只是不再使用了而已)。
202 | 在线提供的类库 (如 jquery-3.3.1.min.js, lodash.min.js 等) 均采用这个模式。
203 |
204 | ## 用户行为对浏览器缓存的影响
205 |
206 | 所谓用户行为对浏览器缓存的影响,指的就是用户在浏览器如何操作时,会触发怎样的缓存策略。主要有 3 种:
207 |
208 | * 打开网页,地址栏输入地址: 查找 disk cache 中是否有匹配。如有则使用;如没有则发送网络请求。
209 | * 普通刷新 (F5):因为 TAB 并没有关闭,因此 memory cache 是可用的,会被优先使用(如果匹配的话)。其次才是 disk cache。
210 | * 强制刷新 (Ctrl + F5):浏览器不使用缓存,因此发送的请求头部均带有 Cache-control: no-cache(为了兼容,还带了 Pragma: no-cache),服务器直接返回 200 和最新内容。
211 |
212 |
--------------------------------------------------------------------------------
/docs/browser/browser-chrome.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebarDepth: 3
3 | ---
4 |
5 | # Chrome架构
6 |
7 | ## 打开1个页面却开启4个进程?
8 |
9 | Chrome 浏览器右上角的”选项“采单,选择“更多工具”子菜单,点击“任务管理器”, 即可查看.
10 |
11 | ## 线程与进程
12 |
13 | ### 线程
14 |
15 | 计算机中的并行处理就是同一时刻处理多个任务. 比如计算下面三个表达式的值,
16 |
17 | ```js
18 | A = 1+2
19 | B = 20/5
20 | C = 7*8
21 | ```
22 |
23 | 用**单线程**来处理,则分为四步按照顺序分别执行三个表达式和最终结果。
24 |
25 | 用**多线程**来处理,则分为两步,第一步,使用三个线程同时执行前三个任务;第二步,再执行第四个显示任务。
26 |
27 | 因此,多线程可以并行处理任务,**使用并行处理能大大提升性能**。
28 |
29 | ### 进程
30 |
31 | **线程是不能单独存在的,它是由进程来启动和管理的**。
32 |
33 | 一个进程就是一个程序的运行实例。详细解释就是,启动一个程序的时候,操作系统会为该程序创建一块内存,用来存放代码、运行中的数据和一个执行任务的主线程,我们把这样的一个运行环境叫进程。
34 |
35 | 
36 |
37 | 从图中可以看到,**线程是依附于进程的,而进程中使用多线程并行处理能提升运算效率**。
38 |
39 | 总结来说,进程和线程之间的关系有以下 **4** 个特点。
40 |
41 | 1. 进程中的任意一线程执行出错,都会导致整个进程的崩溃。
42 |
43 | 2. 线程之间共享进程中的数据。
44 | 
45 |
46 | 3. 当一个进程关闭之后,操作系统会回收进程所占用的内存。
47 |
48 | 当一个进程退出时,操作系统会回收该进程所申请的所有资源;即使其中任意线程因为操作不当导致内存泄漏,当进程退出时,这些内存也会被正确回收。
49 |
50 | 4. 进程之间的内容相互隔离。
51 |
52 | 进程隔离是为保护操作系统中进程互不干扰的技术,每一个进程只能访问自己占有的数据。如果进程之间需要进行数据的通信,这时候,[就需要使用用于进程间通信(IPC)的机制了](https://blog.csdn.net/zhaohong_bo/article/details/89552188)。
53 |
54 | ## 单进程浏览器时代
55 |
56 | 单进程浏览器是指浏览器的所有功能模块都是运行在同一个进程里. 这些模块包含了网络、插件、JavaScript 运行环境、渲染引擎和页面等。其实早在 2007 年之前,市面上浏览器都是单进程的。单进程浏览器的架构如下图所示:
57 |
58 | 
59 |
60 | 如此多的功能模块运行在一个进程里,是导致单进程浏览器**不稳定、不流畅和不安全**的一个主要因素。下面我就来一一分析下出现这些问题的原因。
61 |
62 | 1. 不稳定
63 |
64 | 早期浏览器需要借助于插件来实现诸如 Web 视频、Web 游戏等各种强大的功能,但是插件是最容易出问题的模块,并且还运行在浏览器进程之中,所以**一个插件的意外崩溃会引起整个浏览器的崩溃**。
65 |
66 | 除了插件之外,**渲染引擎模块**也是不稳定的,通常一些复杂的 JavaScript 代码就有可能引起渲染引擎模块的崩溃。和插件一样,渲染引擎的崩溃也会导致整个浏览器的崩溃。
67 |
68 | 2. 不流畅
69 |
70 | 从上面的“单进程浏览器架构示意图”可以看出,所有页面的渲染模块、JavaScript 执行环境以及插件都是运行在同一个线程中的,这就意味着同一时刻只能有一个模块可以执行。
71 |
72 | 3. 不安全
73 |
74 | 插件不安全:插件可以使用 C/C++ 等代码编写,通过插件可以获取到操作系统的任意资源,当你在页面运行一个插件时也就意味着这个插件能完全操作你的电脑。如果是个恶意插件,那么它就可以释放病毒、窃取你的账号密码,引发安全性问题。
75 |
76 | 页面脚本不安全:它可以通过浏览器的漏洞来获取系统权限,这些脚本获取系统权限之后也可以对你的电脑做一些恶意的事情,同样也会引发安全问题。
77 |
78 | ## 多进程浏览器时代
79 |
80 | ### 早期多进程架构
81 |
82 | 2008年 Chrome 发布时的进程架构。
83 |
84 | 
85 |
86 | 从图中可以看出,Chrome 的页面是运行在单独的渲染进程中的,同时页面里的插件也是运行在单独的插件进程之中,而进程之间是通过 IPC 机制进行通信(如图中虚线部分)。
87 |
88 | 1. 解决不稳定问题
89 |
90 | 由于进程是相互隔离的,所以当一个页面或者插件崩溃时,影响到的仅仅是当前的页面进程或者插件进程,并不会影响到浏览器和其他页面,这就完美地解决了页面或者插件的崩溃会导致整个浏览器崩溃,也就是不稳定的问题。
91 |
92 | 2. 解决不流畅问题
93 |
94 | JavaScript 也是运行在渲染进程中的,所以即使 JavaScript 阻塞了渲染进程,影响到的也只是当前的渲染页面,而并不会影响浏览器和其他页面,因为其他页面的脚本是运行在它们自己的渲染进程中的(同源策略,同一网站的页面使用一个渲染机制)。所以当我们再在 Chrome 中运行上面那个死循环的脚本时,没有响应的仅仅是当前的页面。
95 |
96 | 3. 解决内存泄露问题
97 |
98 | 因为当关闭一个页面时,整个渲染进程也会被关闭,之后该进程所占用的内存都会被系统回收。早期单进程浏览器关闭页面的话仅仅是关闭一个线程,并不会回收所有内存。
99 |
100 | 4. 解决安全问题
101 |
102 | 安全沙箱:采用多进程架构的额外好处是可以使用**安全沙箱**,你可以把沙箱看成是操作系统给进程上了一把锁,沙箱里面的程序可以运行,但是不能在你的硬盘上写入任何数据,也不能在敏感位置读取任何数据,例如你的文档和桌面。Chrome 把插件进程和渲染进程锁在沙箱里面,这样即使在渲染进程或者插件进程里面执行了恶意程序,恶意程序也无法突破沙箱去获取系统权限。
103 |
104 | 为何单进程不能使用安全沙箱?因为单进程无法做到隔离。
105 |
106 | ### 目前多进程架构
107 |
108 | 
109 |
110 | 从图中可以看出,最新的 Chrome 浏览器包括:1 个浏览器(Browser)主进程、1 个 GPU 进程、1 个网络(NetWork)进程、多个渲染进程和多个插件进程。
111 |
112 | 1. 浏览器进程
113 |
114 | 主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
115 |
116 | 2. 渲染进程
117 |
118 | 核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。
119 |
120 | 3. GPU 进程
121 |
122 | 其实,Chrome 刚开始发布的时候是没有 GPU 进程的。而 GPU 的使用初衷是为了实现 3D CSS 的效果,只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。最后,Chrome 在其多进程架构上也引入了 GPU 进程。
123 |
124 | 4. 网络进程
125 |
126 | 主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。
127 |
128 | 5. 插件进程
129 |
130 | 主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。
131 |
132 | :::tip
133 |
134 | 为什么有 4 个进程?
135 |
136 | 因为打开 1 个页面至少需要 1 个网络进程、1 个浏览器进程、1 个 GPU 进程以及 1 个渲染进程,共 4 个;
137 |
138 | 如果打开的页面有运行插件的话,还需要再加上 1 个插件进程。
139 |
140 | :::
141 |
142 | ## 未来面向服务的架构
143 |
144 | 为了解决这些问题,在 2016 年,Chrome 官方团队使用“面向服务的架构”(Services Oriented Architecture,简称 SOA)的思想设计了新的 Chrome 架构。也就是说 Chrome 整体架构会朝向现代操作系统所采用的“面向服务的架构” 方向发展,原来的各种模块会被重构成独立的服务(Service),每个服务(Service)都可以在独立的进程中运行,访问服务(Service)必须使用定义好的接口,通过 IPC 来通信,从而**构建一个更内聚、松耦合、易于维护和扩展的系统**,更好实现 Chrome 简单、稳定、高速、安全的目标。
145 |
146 | Chrome 最终要把 UI、数据库、文件、设备、网络等模块重构为基础服务,类似操作系统底层服务,下面是 Chrome“面向服务的架构”的进程模型图:
147 |
148 | 
149 |
150 | 目前 Chrome 正处在老的架构向服务化架构过渡阶段,这将是一个漫长的迭代过程。
151 |
152 | Chrome 正在逐步构建 Chrome 基础服务(Chrome Foundation Service),如果你认为 Chrome 是“便携式操作系统”,那么 Chrome 基础服务便可以被视为该操作系统的“基础”系统服务层。
153 |
154 | 同时 Chrome 还提供灵活的弹性架构,在强大性能设备上会以多进程的方式运行基础服务,但是如果在资源受限的设备上(如下图),Chrome 会将很多服务整合到一个进程中,从而节省内存占用。
155 |
156 | 
157 |
158 | ## 总结
159 |
160 | 最初的浏览器都是单进程的,它们不稳定、不流畅且不安全,之后出现了 Chrome,创造性地引入了多进程架构,并解决了这些遗留问题。随后 Chrome 试图应用到更多业务场景,如移动设备、VR、视频等,为了支持这些场景,Chrome 的架构体系变得越来越复杂,这种架构的复杂性倒逼 Chrome 开发团队必须进行架构的重构,最终 Chrome 团队选择了面向服务架构(SOA)形式,这也是 Chrome 团队现阶段的一个主要任务。
161 |
162 | 鉴于目前架构的复杂性,要完整过渡到面向服务架构,估计还需要好几年时间才能完成。不过 Chrome 开发是一个渐进的过程,新的特性会一点点加入进来,这也意味着我们随时能看到 Chrome 新的变化。
163 |
164 | 总体说来,Chrome 是以一个非常快速的速度在进化,越来越多的业务和应用都逐渐转至浏览器来开发,身为开发人员,我们不能坐视不管,而应该紧跟其步伐,收获这波技术红利。
--------------------------------------------------------------------------------
/docs/browser/browser-security.md:
--------------------------------------------------------------------------------
1 | # 浏览器安全
2 |
3 | ## 跨站脚本攻击 XSS
4 |
5 | ### 什么是 XSS 攻击
6 |
7 | XSS 全称是 Cross Site Scripting,为了与“CSS”区分开来,故简称 XSS,翻译过来就是“跨站脚本”。**XSS 攻击是指黑客往 HTML 文件中或者 DOM 中注入恶意脚本**,从而在用户浏览页面时利用注入的恶意脚本对用户实施攻击的一种手段。
8 |
9 | ### 恶意脚本都能做哪些事情
10 |
11 | 1. **窃取 Cookie 信息**,可以通过“document.cookie”获取 Cookie 信息
12 | 2. **监听用户行为**,可以使用“addEventListener”接口来监听键盘事件
13 | 3. **修改 DOM**
14 | 4. **在页面内生成浮窗广告**
15 |
16 | ### 如何注入恶意脚本
17 |
18 | 1. **存储型 XSS 攻击**,利用表单提交一段`
56 | ```
57 |
58 | ### 懒加载与预加载的区别
59 |
60 | 这两种方式都是提高网页性能的方式,两者主要区别是一个是提前加载,一个是迟缓甚至不加载。懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。
61 |
62 | 1. **懒加载也叫延迟加载,指的是在长网页中延迟加载图片的时机,当用户需要访问时,再去加载**,这样可以提高网站的首屏加载速度,提升用户的体验,并且可以减少服务器的压力。它适用于图片很多,页面很长的电商网站的场景。懒加载的实现原理是,将页面上的图片的 src 属性设置为空字符串,将图片的真实路径保存在一个自定义属性中,当页面滚动的时候,进行判断,如果图片进入页面可视区域内,则从自定义属性中取出真实路径赋值给图片的 src 属性,以此来实现图片的延迟加载。
63 |
64 | 2. **预加载指的是将所需的资源提前请求加载到本地,这样后面在需要用到时就直接从缓存取资源**。通过预加载能够减少用户的等待时间,提高用户的体验。我了解的预加载的最常用的方式是使用 js 中的 image 对象,通过为 image 对象来设置 scr 属性,来实现图片的预加载。
--------------------------------------------------------------------------------
/docs/pages/performance-optimization/reflow-and-repaint.md:
--------------------------------------------------------------------------------
1 | # 回流与重绘
2 |
3 | ## 一、基本概念与触发条件
4 |
5 | ### 回流-Reflow
6 |
7 | 当渲染树中部分或者全部元素的尺寸、结构或者属性发生变化时,浏览器会重新渲染部分或者全部文档的过程就称为**回流**。
8 |
9 | ### 哪些操作会导致回流
10 |
11 | 1. 页面的首次渲染
12 | 2. 浏览器的窗口大小发生变化
13 | 3. 元素的内容发生变化
14 | 4. 元素的尺寸或者位置发生变化
15 | 5. 元素的字体大小发生变化
16 | 6. 激活CSS伪类
17 | 7. 查询某些属性或者调用某些方法
18 | 8. 添加或者删除可见的DOM元素
19 |
20 | 在触发回流(重排)的时候,由于浏览器渲染页面是基于流式布局的,所以当触发回流时,会导致周围的DOM元素重新排列,它的影响范围有两种:
21 |
22 | * 全局范围:从根节点开始,对整个渲染树进行重新布局
23 | * 局部范围:对渲染树的某部分或者一个渲染对象进行重新布局
24 |
25 | ### 重绘-Repaint
26 |
27 | 当页面中某些元素的样式发生变化,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是**重绘**。
28 |
29 | ### 哪些操作会导致重绘
30 |
31 | 1. color、background 相关属性:background-color、background-image 等
32 | 2. outline 相关属性:outline-color、outline-width 、text-decoration
33 | 3. border-radius、visibility、box-shadow
34 |
35 | :::tip
36 | 当触发回流时,一定会触发重绘,但是重绘不一定会引发回流。
37 | :::
38 |
39 | ## 二、如何避免回流与重绘
40 |
41 | 1. 操作DOM时,尽量在低层级的DOM节点进行操作
42 | 2. 不要使用 `table` 布局, 一个小的改动可能会使整个 `table` 进行重新布局
43 | 3. 使用CSS的表达式
44 | 4. 不要频繁操作元素的样式,对于静态页面,可以修改类名,而不是样式
45 | 5. 使用absolute或者fixed,使元素脱离文档流,这样他们发生变化就不会影响其他元素
46 | 6. 避免频繁操作DOM,可以创建一个文档片段 `documentFragment`,在它上面应用所有DOM操作,最后再把它添加到文档中
47 | 7. 将元素先设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘.
48 | 8. 将DOM的多个读操作(或者写操作)放在一起,而不是读写操作穿插着写。这得益于浏览器的渲染队列机制。
49 |
50 | 浏览器针对页面的回流与重绘,进行了自身的优化——**渲染队列**
51 |
52 | :::tip
53 | 浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会对队列进行批处理。这样就会让多次的回流、重绘变成一次回流重绘。
54 | :::
55 |
56 | ## 三、如何优化动画
57 |
58 | 对于如何优化动画,我们知道,一般情况下,动画需要频繁的操作DOM,就就会导致页面的性能问题,我们可以将动画的 `position` 属性设置为 `absolute` 或者 `fixed`,将动画脱离文档流,这样他的回流就不会影响到页面了。
59 |
60 | ## 四、documentFragment
61 |
62 | [文档](https://developer.mozilla.org/zh-CN/docs/Web/API/DocumentFragment)
--------------------------------------------------------------------------------
/docs/pages/performance-optimization/throttle-and-debounce.md:
--------------------------------------------------------------------------------
1 | # 节流与防抖
2 |
3 | ## 节流概念-throttle
4 |
5 | 函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。
6 |
7 | ## 节流应用场景
8 |
9 | * 拖拽场景:固定时间内只执⾏⼀次,防⽌超⾼频次触发位置变动
10 | * 缩放场景:监控浏览器resize
11 | * 动画场景:避免短时间内多次触发动画引起性能问题
12 |
13 | ## 防抖概念-debounce
14 |
15 | 函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
16 |
17 | ## 防抖应用场景
18 |
19 | * 按钮提交场景:防⽌多次提交按钮,只执⾏最后提交的⼀次
20 | * 服务端验证场景:表单验证需要服务端配合,只执⾏⼀段连续的输⼊事件的最后⼀次,还有搜索联想词功能类似⽣存环境请⽤lodash.debounce
21 |
22 |
23 | ## 代码实现
24 |
25 | ### 节流
26 |
27 | ```js
28 | // 时间戳版
29 | function throttle(fn, delay) {
30 | var preTime = Date.now();
31 |
32 | return function() {
33 | var context = this,
34 | args = [...arguments],
35 | nowTime = Date.now();
36 |
37 | // 如果两次时间间隔超过了指定时间,则执行函数。
38 | if (nowTime - preTime >= delay) {
39 | preTime = Date.now();
40 | return fn.apply(context, args);
41 | }
42 | };
43 | }
44 |
45 | // 定时器版
46 | function throttle (fun, wait){
47 | let timeout = null
48 | return function(){
49 | let context = this
50 | let args = [...arguments]
51 | if(!timeout){
52 | timeout = setTimeout(() => {
53 | fun.apply(context, args)
54 | timeout = null
55 | }, wait)
56 | }
57 | }
58 | }
59 | ```
60 |
61 | ### 防抖
62 |
63 | ```js
64 | function debounce(fn, wait) {
65 | var timer = null;
66 |
67 | return function() {
68 | var context = this,
69 | args = [...arguments];
70 |
71 | // 如果此时存在定时器的话,则取消之前的定时器重新记时
72 | if (timer) {
73 | clearTimeout(timer);
74 | timer = null;
75 | }
76 |
77 | // 设置定时器,使事件间隔指定事件后执行
78 | timer = setTimeout(() => {
79 | fn.apply(context, args);
80 | }, wait);
81 | };
82 | }
83 | ```
--------------------------------------------------------------------------------
/docs/pages/performance-optimization/webpack-optimization.md:
--------------------------------------------------------------------------------
1 | # webpack优化
2 |
3 | ## 一、如何提高webpack打包速度
4 |
5 | ### 优化Loader
6 |
7 | 对于 Loader 来说,影响打包效率首当其冲必属 Babel 了。因为 Babel 会将代码转为字符串生成 AST,然后对 AST 继续进行转变最后再生成新的代码,项目越大,**转换代码越多,效率就越低**。当然了,这是可以优化的。
8 |
9 | 首先我们**优化 Loader 的文件搜索范围**
10 |
11 | ```js
12 | module.exports = {
13 | module: {
14 | rules: [
15 | {
16 | // js 文件才使用 babel
17 | test: /\.js$/,
18 | loader: 'babel-loader',
19 | // 只在 src 文件夹下查找
20 | include: [resolve('src')],
21 | // 不会去查找的路径
22 | exclude: /node_modules/
23 | }
24 | ]
25 | }
26 | }
27 | ```
28 |
29 | 对于 Babel 来说,希望只作用在 JS 代码上的,然后 node_modules 中使用的代码都是编译过的,所以完全没有必要再去处理一遍。
30 |
31 | 当然这样做还不够,还可以将 Babel 编译过的文件缓存起来,下次只需要编译更改过的代码文件即可,这样可以大幅度加快打包时间
32 |
33 | ```js
34 | loader: 'babel-loader?cacheDirectory=true'
35 | ```
36 |
37 | ### HappyPack
38 |
39 | 受限于 Node 是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特别是在执行 Loader 的时候,长时间编译的任务很多,这样就会导致等待的情况。
40 |
41 | HappyPack 可以将 Loader 的同步执行转换为并行的,这样就能充分利用系统资源来加快打包效率了
42 |
43 | ```js
44 | module: {
45 | loaders: [
46 | {
47 | test: /\.js$/,
48 | include: [resolve('src')],
49 | exclude: /node_modules/,
50 | // id 后面的内容对应下面
51 | loader: 'happypack/loader?id=happybabel'
52 | }
53 | ]
54 | },
55 | plugins: [
56 | new HappyPack({
57 | id: 'happybabel',
58 | loaders: ['babel-loader?cacheDirectory'],
59 | // 开启 4 个线程
60 | threads: 4
61 | })
62 | ]
63 | ```
64 |
65 | ### DllPlugin
66 |
67 | **DllPlugin 可以将特定的类库提前打包然后引入**。这种方式可以极大的减少打包类库的次数,只有当类库更新版本才有需要重新打包,并且也实现了将公共代码抽离成单独文件的优化方案。DllPlugin的使用方法如下:
68 |
69 | ```js
70 | // 单独配置在一个文件中
71 | // webpack.dll.conf.js
72 | const path = require('path')
73 | const webpack = require('webpack')
74 | module.exports = {
75 | entry: {
76 | // 想统一打包的类库
77 | vendor: ['react']
78 | },
79 | output: {
80 | path: path.join(__dirname, 'dist'),
81 | filename: '[name].dll.js',
82 | library: '[name]-[hash]'
83 | },
84 | plugins: [
85 | new webpack.DllPlugin({
86 | // name 必须和 output.library 一致
87 | name: '[name]-[hash]',
88 | // 该属性需要与 DllReferencePlugin 中一致
89 | context: __dirname,
90 | path: path.join(__dirname, 'dist', '[name]-manifest.json')
91 | })
92 | ]
93 | }
94 | ```
95 |
96 | 然后需要执行这个配置文件生成依赖文件,接下来需要使用 DllReferencePlugin 将依赖文件引入项目中
97 |
98 | ```js
99 | // webpack.conf.js
100 | module.exports = {
101 | // ...省略其他配置
102 | plugins: [
103 | new webpack.DllReferencePlugin({
104 | context: __dirname,
105 | // manifest 就是之前打包出来的 json 文件
106 | manifest: require('./dist/vendor-manifest.json'),
107 | })
108 | ]
109 | }
110 | ```
111 |
112 | ### 代码压缩
113 |
114 | 在 Webpack3 中,一般使用 `UglifyJS` 来压缩代码,但是这个是单线程运行的,为了加快效率,可以使用 `webpack-parallel-uglify-plugin` 来并行运行 `UglifyJS`,从而提高效率。
115 |
116 | 在 Webpack4 中,不需要以上这些操作了,只需要将 `mode` 设置为 `production` 就可以默认开启以上功能。代码压缩也是我们必做的性能优化方案,当然我们不止可以压缩 JS 代码,还可以压缩 HTML、CSS 代码,并且在压缩 JS 代码的过程中,我们还可以通过配置实现比如删除 `console.log` 这类代码的功能。
117 |
118 |
119 | ### 其他
120 |
121 | 可以通过一些小的优化点来加快打包速度
122 |
123 | * **resolve.extensions**:用来表明文件后缀列表,默认查找顺序是 ['.js', '.json'],如果你的导入文件没有添加后缀就会按照这个顺序查找文件。我们应该尽可能减少后缀列表长度,然后将出现频率高的后缀排在前面
124 | * **resolve.alias**:可以通过别名的方式来映射一个路径,能让 Webpack 更快找到路径
125 | * **module.noParse**:如果你确定一个文件下没有其他依赖,就可以使用该属性让 Webpack 不扫描该文件,这种方式对于大型的类库很有帮助
126 |
127 | ## 二、如何减少 Webpack 打包体积
128 |
129 | ### 按需加载
130 |
131 | 在开发 SPA 项目的时候,项目中都会存在很多路由页面。如果将这些页面全部打包进一个 JS 文件的话,虽然将多个请求合并了,但是同样也加载了很多并不需要的代码,耗费了更长的时间。那么为了首页能更快地呈现给用户,希望首页能加载的文件体积越小越好,这时候就可以使用按需加载,将每个路由页面单独打包为一个文件。当然不仅仅路由可以按需加载,对于 `loadash` 这种大型类库同样可以使用这个功能。
132 |
133 | 按需加载的代码实现这里就不详细展开了,因为鉴于用的框架不同,实现起来都是不一样的。当然了,虽然他们的用法可能不同,但是底层的机制都是一样的。都是当使用的时候再去下载对应文件,返回一个 Promise,当 Promise 成功以后去执行回调。
134 |
135 | ### Scope Hoisting
136 |
137 | `Scope Hoisting` 会分析出模块之间的依赖关系,尽可能的把打包出来的模块合并到一个函数中去。
138 |
139 | 比如希望打包两个文件:
140 |
141 | ```js
142 | // test.js
143 | export const a = 1
144 | // index.js
145 | import { a } from './test.js'
146 | ```
147 |
148 | 对于这种情况,打包出来的代码会类似这样:
149 |
150 | ```js
151 | [
152 | /* 0 */
153 | function (module, exports, require) {
154 | //...
155 | },
156 | /* 1 */
157 | function (module, exports, require) {
158 | //...
159 | }
160 | ]
161 | ```
162 |
163 | 但是如果使用 Scope Hoisting ,代码就会尽可能的合并到一个函数中去,也就变成了这样的类似代码:
164 |
165 | ```js
166 | [
167 | /* 0 */
168 | function (module, exports, require) {
169 | //...
170 | }
171 | ]
172 | ```
173 |
174 | 这样的打包方式生成的代码明显比之前的少多了。如果在 Webpack4 中你希望开启这个功能,只需要启用 `optimization.concatenateModules` 就可以了:
175 |
176 | ```js
177 | module.exports = {
178 | optimization: {
179 | concatenateModules: true
180 | }
181 | }
182 | ```
183 |
184 | ### Tree Shaking
185 |
186 | Tree Shaking 可以实现删除项目中未被引用的代码,比如:
187 |
188 | ```js
189 | // test.js
190 | export const a = 1
191 | export const b = 2
192 | // index.js
193 | import { a } from './test.js'
194 | ```
195 |
196 | 对于以上情况,test 文件中的变量 b 如果没有在项目中使用到的话,就不会被打包到文件中。
197 |
198 | * 如果使用 Webpack 4 的话,开启生产环境就会自动启动这个优化功能。
199 |
200 | ## 三、如何⽤webpack来优化前端性能
201 |
202 | ⽤webpack优化前端性能是指优化webpack的输出结果,让打包的最终结果在浏览器运⾏快速⾼效。
203 |
204 | 1. **压缩代码**: 删除多余的代码、注释、简化代码的写法等等⽅式。可以利⽤webpack的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩JS⽂件, 利⽤ cssnano (css-loader?minimize)来压缩css
205 | 2. **利⽤CDN加速**: 在构建过程中,将引⽤的静态资源路径修改为CDN上对应的路径。可以利⽤webpack对于 output 参数和各loader的 publicPath 参数来修改资源路径
206 | 3. **Tree Shaking**: 将代码中永远不会⾛到的⽚段删除掉。可以通过在启动webpack时追加参数 --optimize-minimize 来实现
207 | 4. **Code Splitting**: 将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利⽤浏览器缓存
208 | 5. **提取公共第三⽅库**: SplitChunksPlugin插件来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码
209 |
210 | ## 四、如何提⾼webpack的构建速度
211 |
212 | 1. 多⼊⼝情况下,使⽤ `CommonsChunkPlugin` 来提取公共代码
213 | 2. 通过 externals 配置来提取常⽤库
214 | 3. 利⽤ DllPlugin 和 DllReferencePlugin 预编译资源模块 通过 DllPlugin 来对那些我们引⽤但是绝对不会修改的npm包来进⾏预编译,再通过 DllReferencePlugin 将预编译的模块加载进来
215 | 4. 使⽤ Happypack 实现多线程加速编译
216 | 5. 使⽤ webpack-uglify-parallel 来提升 uglifyPlugin 的压缩速度。 原理上 webpack-uglify-parallel 采⽤了多核并⾏压缩来提升压缩速度
217 | 6. 使⽤ Tree-shaking 和 Scope Hoisting 来剔除多余代码
--------------------------------------------------------------------------------
/docs/question/index.md:
--------------------------------------------------------------------------------
1 | # 开发问题总结
2 |
3 | ## React 的 BrowserRouter 模式下刷新 404
4 |
5 | 开发模式下,设置 devServer
6 |
7 | ```js
8 | devServer {
9 | historyApiFallback: true
10 | }
11 | ```
12 |
13 | nginx下,设置如下
14 |
15 | ```js
16 | location /pwa {
17 | ssi on;
18 | ssi_silent_errors on;
19 | try_files $uri /pwa/index.html; // browserHistory模式 404问题
20 | autoindex on; // 输入到/pwa 会直接定向到index.html
21 | root /www/workspaces/webpack-demo/dist/;
22 | index index.html index.htm;
23 | }
24 | ```
25 |
26 | ## Component definition is missing display name
27 |
28 | 错误提示
29 |
30 | ```bash
31 | ❌ https://google.com/#q=react/display-name
32 | Component definition is missing display name
33 | ```
34 |
35 | code:
36 |
37 | ```js
38 | const Renderers = {
39 | code: ({ value }) => { // 报错
40 | return
41 | }
42 | }
43 | ```
44 |
45 | 解决方案为方法添加方法名:
46 |
47 | ```js
48 | const Renderers = {
49 | code: function render ({ value }) {
50 | return
51 | }
52 | }
53 | ```
54 |
55 | [eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react/issues/597)
56 |
57 | ## Do not pass children as props. Instead, nest children between the opening and closing tags
58 |
59 | 错误提示
60 |
61 | ```bash
62 | ❌ https://google.com/#q=react/no-children-prop
63 | Do not pass children as props. Instead, nest children between the opening and closing tags
64 | ```
65 |
66 | code:
67 |
68 | ```js
69 | const Codes = (props: Props) => {
70 | return (
71 | // 报错
72 | )
73 | }
74 | ```
75 |
76 | 解决方案:
77 |
78 | ```js
79 | const Codes = (props: Props) => {
80 | return (
81 | {props.md}
82 | )
83 | }
84 | ```
85 |
86 | [eslint-plugin-react](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-children-prop.md)
87 |
88 | ## 移动端Input获取焦点拉起系统键盘留白
89 |
90 | ```js
91 | // 添加 blur 事件,触发后修改滚动条位置,可置顶可还原
92 | $_onBlur () {
93 | const scrollTop = document.documentElement.scrollTop || document.body.scrollTop
94 | if (scrollTop > 0) {
95 | window.requestAnimationFrame(this.$_onBlur)
96 | window.scrollTo(0, scrollTop - scrollTop / 8)
97 | }
98 | }
99 | ```
100 |
101 | ## 移动端安卓手机Input拉起系统键盘被遮挡
102 |
103 | ```js
104 | if (/Android/gi.test(navigator.userAgent)) {
105 | event.target.scrollIntoView({
106 | block: "center"
107 | })
108 | }
109 | ```
110 |
111 | ## 解决移动端safari、部分安卓手机浏览器跳转页面返回不刷新问题
112 |
113 | ```js
114 | export function onPageShow(
115 | callback = () => {
116 | window.location.reload()
117 | }
118 | ) {
119 | if (/iphone|ipod|ipad.*os 5/gi.test(navigator.appVersion)) {
120 | window.onpageshow = function(event) {
121 | if (event.persisted) {
122 | callback()
123 | }
124 | }
125 | } else {
126 | document.addEventListener('visibilitychange', () => {
127 | callback()
128 | })
129 | }
130 | }
131 | ```
132 |
133 | ```js
134 | // 调用
135 | onPageShow()
136 | location.assign('xxx')
137 | ```
138 |
139 | ## "export 'default' (imported as 'xxx') was not found in '../../xxx'
140 |
141 | 代码如:
142 |
143 | ```js
144 | import xxx from '../../xxx'
145 | ```
146 |
147 | 问题原因:编译器识别出错,认为 `../../xxx` 路径下未找到抛出的元素.
148 |
149 | ### 检查代码
150 |
151 | 查看该文件是否使用 `export default` 或者 `module.export`、`exports`. 若只是使用 `export` 需要使用结构 或者 `as` 关键字,如下:
152 |
153 | ```js
154 | import { a, b } from '../../xxx'
155 |
156 | import * as xxx from '../../xxx'
157 | ```
158 |
159 | ### 若引入的umd
160 |
161 | 引入的第三方 `umd` JS文件,使用 npm 安装后,通过包名引入是正常的,若直接将其打包后的产物copy到本地,引入使用会报该错误.
162 |
163 | * 解决方案1:
164 |
165 | 修改 package.json
166 |
167 | ```json
168 | {
169 | "dependencies": {
170 | "xxx": "file:../xxx",
171 | }
172 | }
173 | ```
174 |
175 | * 解决方案2:
176 |
177 | 安装 `@babel/plugin-transform-modules-umd`
178 |
179 |
180 | babel.config.js
181 |
182 | ```js
183 | module.exports = {
184 | presets: [
185 | '@vue/cli-plugin-babel/preset',
186 | ],
187 | plugins: ['@babel/plugin-transform-modules-umd']
188 | }
189 | ```
190 |
191 | * 解决方案3:
192 |
193 | 使用 `lerna`
--------------------------------------------------------------------------------
/docs/react/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | home: true
3 | ---
4 |
5 | [Redux](/react/redux.html)
6 |
7 | [基础 Hooks](/react/base-hooks.html)
--------------------------------------------------------------------------------
/docs/react/base-hooks.md:
--------------------------------------------------------------------------------
1 | # Hook
2 |
3 | > Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性.
4 |
5 | * Hook 使你在无需修改组件结构的情况下复用状态逻辑 (自定义Hook)
6 | * Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据 Effect Hook)
7 | * Hook 使你在非 class 的情况下可以使用更多的 React 特性
8 | * Hook 和现有代码可以同时工作,你可以渐进式地使用他们
9 |
10 | ## useState
11 |
12 | * 为什么要使用useState?
13 |
14 | 在 Hooks 出现之前,当我们想要在函数组件内拥有自己的 `state` 时,需要改写为 `class`. 并且状态绑定在 `this` 中
15 |
16 | * 使用
17 |
18 | ```js
19 | const [name, setName] = useState('Jack')
20 | ```
21 |
22 | `name` 为当前组件的状态,通过 `setName('Tom')` 方法来更改,命名自定义,相当于 `class` 中的 `this.setState()`. 在初始化时 使用 `useState('Jack')` 为 `name` 赋值为 `Jack`.
23 |
24 | ```js
25 | const Example = (props) => {
26 | // 你可以在这使用 Hook
27 | return ;
28 | }
29 |
30 | // or
31 | function Example(props) {
32 | // 你可以在这使用 Hook
33 | return ;
34 | }
35 | ```
36 |
37 | * Hook 在 `class` 内部是不起作用的。但你可以使用它们来取代 `class`。
38 |
39 | ## useEffect
40 |
41 | Effect Hook 将 `class` 组件中的 `componentDidMount`、`componentDidUpdate` 和 `componentWillUnmount` 合为一体,跟 `useState` 一样,你可以在组件中多次使用 `useEffect`.
42 |
43 | ```js
44 | useEffect(() => {
45 | const subscription = props.source.subscribe()
46 | return () => {
47 | subscription.unsubscribe()
48 | }
49 | }, [props.source])
50 | ```
51 |
52 | 可以使用 (eslint-plugin-react-hooks)[https://www.npmjs.com/package/eslint-plugin-react-hooks#installation] 中的 (exhaustive-deps)[https://github.com/facebook/react/issues/14920] 规则,会在添加错误依赖时发出警告并给出修复建议。
53 |
54 | ## useContext
55 |
56 | 可以在组件之间共享状态
57 |
58 | ```js
59 | const value = useContext(MyContext)
60 | ```
61 |
62 | 接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 的 value prop 决定。
63 |
64 | 当组件上层最近的 更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider 的 context value 值。即使祖先使用 React.memo 或 shouldComponentUpdate,也会在组件本身使用 useContext 时重新渲染。
65 |
66 | 例子
67 |
68 | ```js
69 | import React, { useContext } from "react"
70 |
71 | const TestContext= React.createContext({});
72 |
73 | const Navbar = () => {
74 | const { username } = useContext(TestContext)
75 |
76 | return (
77 |
80 | )
81 | }
82 |
83 | const Messages = () => {
84 | const { username } = useContext(TestContext)
85 |
86 | return (
87 |
88 |
1 message for {username}
89 |
90 | )
91 | }
92 |
93 | function App () {
94 | return (
95 |
100 |
101 |
102 |
103 |
104 |
105 | )
106 | }
107 |
108 | export default App
109 | ```
110 |
111 | ## useReducer
112 |
113 | ```js
114 | const [state, dispatch] = useReducer(reducer, initialArg, init);
115 | ```
116 |
117 | `useState` 的替代方案. 它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法,
118 |
119 | 在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数。
120 |
121 | ```js
122 | const initialState = {count: 0};
123 |
124 | function reducer(state, action) {
125 | switch (action.type) {
126 | case 'increment':
127 | return {count: state.count + 1}
128 | case 'decrement':
129 | return {count: state.count - 1}
130 | default:
131 | throw new Error()
132 | }
133 | }
134 |
135 | function Counter() {
136 | const [state, dispatch] = useReducer(reducer, initialState)
137 | return (
138 | <>
139 | Count: {state.count}
140 |
141 |
142 | >
143 | )
144 | }
145 | ```
146 |
147 | 你可以选择惰性地创建初始 state。为此,需要将 init 函数作为 useReducer 的第三个参数传入,这样初始 state 将被设置为 init(initialArg)。这么做可以将用于计算 state 的逻辑提取到 reducer 外部,这也为将来对重置 state 的 action 做处理提供了便利:
148 |
149 | ```js
150 | function init(initialCount) {
151 | return {count: initialCount};
152 | }
153 |
154 | function reducer(state, action) {
155 | switch (action.type) {
156 | case 'increment':
157 | return {count: state.count + 1};
158 | case 'decrement':
159 | return {count: state.count - 1};
160 | case 'reset':
161 | return init(action.payload);
162 | default:
163 | throw new Error();
164 | }
165 | }
166 |
167 | function Counter({initialCount = 0}) {
168 | const [state, dispatch] = useReducer(reducer, initialCount, init);
169 | return (
170 | <>
171 | Count: {state.count}
172 |
176 |
177 |
178 | >
179 | );
180 | }
181 | ```
182 |
183 | ## useMemo
184 |
185 | ```js
186 | const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])
187 | ```
188 |
189 | 在a和b的变量值不变的情况下,memoizedValue的值不变。即:useMemo函数的第一个入参函数不会被执行,从而达到节省计算量的目的。
190 |
191 | ```js
192 | import React, { useState, useMemo } from "react"
193 |
194 | function Comp({ name, children }) {
195 | function changeName(name) {
196 | console.log('11')
197 | return name + '改变name的方法'
198 | }
199 | const otherName = changeName(name)
200 | // const otherName = useMemo(() => changeName(name), [name])
201 | return (
202 | <>
203 | {otherName}
204 | {children}
205 | >
206 |
207 | )
208 | }
209 |
210 | function App() {
211 | const [name, setName] = useState('名称')
212 | const [content,setContent] = useState('内容')
213 | return (
214 | <>
215 |
216 |
217 | {content}
218 | >
219 | )
220 | }
221 |
222 | export default App
223 | ```
224 |
225 | 当我们只改变 `content` 值时,changeName 方法会被调用,使用 useMemo 方法,可以避免无用方法的调用。
226 |
227 | ## useCallback
228 |
229 | ```js
230 | const memoizedCallback = useCallback(
231 | () => {
232 | doSomething(a, b);
233 | },
234 | [a, b],
235 | );
236 | ```
237 |
238 | 在a和b的变量值不变的情况下,memoizedCallback的引用不变。即:useCallback的第一个入参函数会被缓存,从而达到渲染性能优化的目的.
239 |
240 | (useMemo & useCallback)[https://kentcdodds.com/blog/usememo-and-usecallback/]
241 |
242 | ## React.memo、useCallback、useMemo
243 |
244 | 问题:React 中 props 或 state 变化时,会重新渲染,实际开发会遇到不必要的渲染场景
245 |
246 | ```js
247 | function ChildComp () {
248 | console.log('render child-comp ...')
249 | return Child Comp ...
250 | }
251 |
252 | function ParentComp () {
253 | const [ count, setCount ] = useState(0)
254 | const increment = () => setCount(count + 1)
255 |
256 | return (
257 |
258 |
259 |
260 |
261 | );
262 | }
263 | ```
264 |
265 | 击父组件中按钮,会修改 count 变量的值,进而导致父组件重新渲染,此时子组件压根没有任何变化(props、state),但在控制台中仍然看到子组件被渲染的打印信息.
266 |
267 | 我们希望子组件的 prop 和 state 没有变化时,即便父组件渲染,也不要渲染子组件。
268 |
269 | **用 memo 修改**
270 |
271 | ```js
272 | import React, { memo, useState } from 'react'
273 |
274 | let ChildComp = function () {
275 | console.log('render child-comp ...')
276 | return Child Comp ...
277 | }
278 |
279 | ChildComp = memo(ChildComp)
280 |
281 | function ParentComp () {
282 | const [ count, setCount ] = useState(0)
283 | const increment = () => setCount(count + 1)
284 |
285 | return (
286 |
287 |
288 |
289 |
290 | );
291 | }
292 | ```
293 |
294 | **传入子组件 props**,props 的值未发生变化,子组件重新渲染。
295 |
296 | ```js
297 | import React, { memo, useState } from 'react'
298 |
299 | const ChildComp = memo(function ({ name, onClick }) {
300 | console.log('render child-comp ...')
301 | return <>
302 | Child Comp ... {name}
303 |
304 | >
305 | })
306 |
307 | function ParentComp () {
308 | const [ count, setCount ] = useState(0)
309 | const increment = () => setCount(count + 1)
310 |
311 | const [ name, setName ] = useState('hi~')
312 | const changeName = (newName) => setName(newName) // 父组件渲染时会创建一个新的函数
313 |
314 | return (
315 |
316 |
317 |
318 |
319 | );
320 | }
321 | ```
322 |
323 | 解决这个问题,我们可以使用 `useCallback` 进行优化
324 |
325 | ```js
326 | const changeName = useCallback((newName) => setName(newName), [])
327 | ```
328 |
329 | 将 changeName 放入 useCallback 中返回缓存的函数. 若使用 useMemo 相同原理,只不过 useMemo 会返回缓存的值.
330 |
331 | ```js
332 | function ParentComp () {
333 | // ....
334 | const [ name, setName ] = useState('hi~')
335 | const [ age, setAge ] = useState(20)
336 | const changeName = useCallback((newName) => setName(newName), [])
337 | const info = useMemo(() => ({ name, age }), [name, age]) // 包一层
338 |
339 | return (
340 |
341 |
342 |
343 |
344 | );
345 | }
346 | ```
347 |
348 | ## useRef
349 |
350 | ```js
351 | const refContainer = useRef(initialValue)
352 | ```
353 |
354 | useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。
355 |
356 | ```js
357 | function TextInputWithFocusButton() {
358 | const inputEl = useRef(null);
359 | const onButtonClick = () => {
360 | // `current` 指向已挂载到 DOM 上的文本输入元素
361 | inputEl.current.focus();
362 | };
363 | return (
364 | <>
365 |
366 |
367 | >
368 | );
369 | }
370 | ```
371 |
372 | 如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。
373 |
374 | ```js
375 | import React, { useRef, memo, useState, useCallback, useEffect, useMemo } from 'react';
376 |
377 | function MeasureExample() {
378 | const [height, setHeight] = useState(0);
379 |
380 | const measuredRef = useCallback(node => {
381 | if (node !== null) {
382 | setHeight(node.getBoundingClientRect().height);
383 | }
384 | }, []);
385 |
386 | return (
387 | <>
388 | Hello, world
389 | The above header is {Math.round(height)}px tall
390 | >
391 | );
392 | }
393 |
394 | export default MeasureExample
395 | ```
396 |
397 | ## useDebugValue
398 |
399 | ```js
400 | useDebugValue(value)
401 | ```
402 |
403 | useDebugValue 可用于在 React 开发者工具(React Developer Tools)中显示自定义 hook 的标签。
404 |
405 | ```js
406 | function useFriendStatus(friendID) {
407 | const [isOnline, setIsOnline] = useState(null);
408 |
409 | // ...
410 |
411 | // 在开发者工具中的这个 Hook 旁边显示标签
412 | // e.g. "FriendStatus: Online"
413 | useDebugValue(isOnline ? 'Online' : 'Offline');
414 |
415 | return isOnline;
416 | }
417 | ```
--------------------------------------------------------------------------------
/docs/react/redux.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ## 工作流程
4 |
5 | 首先,用户发送 Action。
6 |
7 | ```js
8 | store.dispatch(action)
9 | ```
10 |
11 | 然后,Store 自动调用 Reducer, 并且传入两个参数:当前 State 和收到的 Action。Reducer 会返回新的 State。
12 |
13 | ```js
14 | let nextState = todoApp(previousState, action)
15 | ```
16 |
17 | State 一旦发生变化,Store 就会调用监听函数。
18 |
19 | ```js
20 | // 设置监听函数
21 | store.subscribe(listener)
22 | ```
23 |
24 | listener 可以通过 store.getState() 得到当前状态。如果使用的是 React, 这时可以出发重新渲染 View。
25 |
26 | ```js
27 | function listerner() {
28 | let newState = store.getState()
29 | component.setState(newState)
30 | }
31 | ```
32 |
33 | ## 基本概念
34 |
35 | ### Store
36 |
37 | Store 就是保存数据的地方,你可以把它看成一个容器。**整个应用只能有一个 Store。**
38 |
39 | Redux 提供 `createStore` 这个函数,用来生成 Store。
40 |
41 | ```js
42 | import { createStore } from 'redux'
43 | const store = createStore(fn)
44 | ```
45 |
46 | 上面代码中,`createStore` 函数接受另一个函数作为参数,返回新生成的 Store 对象。
47 |
48 | ### State
49 |
50 | `Store` 对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。
51 |
52 | 当前时刻的 State,可以通过 `store.getState()` 拿到。
53 |
54 | ```js
55 | import { createStore } from 'redux'
56 | const store = createStore(fn)
57 |
58 | const state = store.getState()
59 | ```
60 |
61 | Redux 规定, 一个 State 对应一个 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什么样,反之亦然。
62 |
63 | ### Action
64 |
65 | State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。
66 |
67 | Action 是一个对象。其中的 `type` 属性是必须的,表示 Action 的名称。其他属性可以自由设置,社区有一个[规范](https://github.com/acdlite/flux-standard-action)可以参考。
68 |
69 | ```js
70 | const action = {
71 | type: 'ADD_TODO',
72 | payload: 'Learn Redux'
73 | }
74 | ```
75 |
76 | 上面代码中,Action 的名称是 `ADD_TODO`,它携带的信息是字符串 `Learn Redux`。
77 |
78 | 可以这样理解,Action 描述当前发生的事情。改变 State 的唯一办法,就是使用 Action。它会运送数据到 Store。
79 |
80 | ### store.dispatch()
81 |
82 | store.dispatch() 是 View 发出 Action 的唯一方法。
83 |
84 | ```js
85 | import { createStore } from 'redux'
86 | const store = createStore(fn)
87 |
88 | store.dispatch({
89 | type: 'ADD_TODO',
90 | payload: 'Learn Redux'
91 | })
92 | ```
93 |
94 | 上面代码中,store.dispatch 接受一个 Action 对象作为参数,将它发送出去。
95 |
96 | ### Reducer
97 |
98 | Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。
99 |
100 | Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
101 |
102 | ```js
103 | const reducer = function (state, action) {
104 | // ...
105 | return new_state
106 | }
107 | ```
108 |
109 | 整个应用的初始状态,可以作为 State 的默认值。下面是一个实际的例子。
110 |
111 | ```js
112 | const defaultState = 0
113 | const reducer = (state = defaultState, action) => {
114 | switch (action.type) {
115 | case 'ADD':
116 | return state + action.payload
117 | default:
118 | return state;
119 | }
120 | }
121 |
122 | const state = reducer(1, {
123 | type: 'ADD',
124 | payload: 2
125 | })
126 | ```
127 |
128 | 上面代码中,reducer 函数收到名为 ADD 的 Action 以后,就返回一个新的 State,作为加法的计算结果。其他运算的逻辑(比如减法),也可以根据 Action 的不同来实现。
129 |
130 | 实际应用中,Reducer 函数不用像上面这样手动调用,store.dispatch 方法会触发 Reducer 的自动执行。为此,Store 需要知道 Reducer 函数,做法就是在生成 Store 的时候,将 Reducer 传入 createStore 方法。
131 |
132 | ```js
133 | import { createStore } from 'redux'
134 | const store = createStore(reducer)
135 | ```
136 |
137 | 上面代码中,createStore 接受 Reducer 作为参数,生成一个新的 Store。以后每当 store.dispatch 发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。
138 |
139 | ### store.subscribe()
140 |
141 | Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。
142 |
143 | ```js
144 | import { createStore } from 'redux'
145 | const store = createStore(reducer)
146 |
147 | store.subscribe(listener)
148 | ```
149 |
150 | 显然,只要把 View 的更新函数(对于 React 项目,就是组件的 render 方法或 setState 方法)放入 listen,就会实现 View 的自动渲染。
151 |
152 | store.subscribe 方法返回一个函数,调用这个函数就可以解除监听。
153 |
154 | ```js
155 | let unsubscribe = store.subscribe(() =>
156 | console.log(store.getState())
157 | )
158 |
159 | unsubscribe()
160 | ```
161 |
162 | ## redux 示例
163 |
164 | [更多示例](https://github.com/reduxjs/redux/tree/master/examples)
--------------------------------------------------------------------------------
/docs/server/next/middleware.md:
--------------------------------------------------------------------------------
1 | # middleware
2 |
3 | 中间件是在路由处理程序 之前 调用的函数。 中间件函数可以访问请求和响应对象,以及应用程序请求响应周期中的 next() 中间件函数。 next() 中间件函数通常由名为 next 的变量表示。
4 |
5 | Nest 中间件实际上等价于 [express](http://expressjs.com/en/guide/using-middleware.html) 中间件。 下面是Express官方文档中所述的中间件功能:
6 |
7 | 中间件函数可以执行以下任务:
8 |
9 | 1. 执行任何代码
10 | 2. 对请求和响应对象进行更改
11 | 3. 结束请求-响应周期
12 | 4. 调用堆栈中的下一个中间件函数。
13 | 5. 如果当前的中间件函数没有结束请求-响应周期, 它必须调用 next() 将控制传递给下一个中间件函数。否则, 请求将被挂起。
14 |
15 | 您可以在函数中或在具有 @Injectable() 装饰器的类中实现自定义 Nest中间件。 这个类应该实现 NestMiddleware 接口, 而函数没有任何特殊的要求。 让我们首先使用类方法实现一个简单的中间件功能。
16 |
17 | ```js
18 | // logger.middleware.ts
19 |
20 | import { Injectable, NestMiddleware } from '@nestjs/common';
21 | import { Request, Response } from 'express';
22 |
23 | @Injectable()
24 | export class LoggerMiddleware implements NestMiddleware {
25 | use(req: Request, res: Response, next: Function) {
26 | console.log('Request...');
27 | next();
28 | }
29 | }
30 | ```
31 |
32 | ## 应用中间件
33 |
34 | 中间件不能在 @Module() 装饰器中列出。我们必须使用模块类的 configure() 方法来设置它们。包含中间件的模块必须实现 NestModule 接口。我们将 LoggerMiddleware 设置在 ApplicationModule 层上。
35 |
36 | ```js
37 | // app.module.ts
38 |
39 | import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
40 | import { LoggerMiddleware } from './common/middleware/logger.middleware';
41 | import { CatsModule } from './cats/cats.module';
42 |
43 | @Module({
44 | imports: [CatsModule],
45 | })
46 | export class AppModule implements NestModule {
47 | configure(consumer: MiddlewareConsumer) {
48 | consumer
49 | .apply(LoggerMiddleware)
50 | .forRoutes('cats');
51 | }
52 | }
53 | ```
--------------------------------------------------------------------------------
/docs/server/next/module.md:
--------------------------------------------------------------------------------
1 | # module
2 |
3 | 模块是具有 @Module() 装饰器的类。 @Module() 装饰器提供了元数据,Nest 用它来组织应用程序结构。
4 |
5 | | | |
6 | | -- | -- |
7 | | providers | 由 Nest 注入器实例化的提供者,并且可以至少在整个模块中共享 |
8 | | controllers | 必须创建的一组控制器 |
9 | | imports | 导入模块的列表,这些模块导出了此模块中所需提供者 |
10 | | exports | 由本模块提供并应在其他模块中可用的提供者的子集。 |
11 |
12 | ## 共享模块
13 |
14 | 在 Nest 中,默认情况下,模块是单例,因此您可以轻松地在多个模块之间共享同一个提供者实例。
15 |
16 | 实际上,每个模块都是一个共享模块。一旦创建就能被任意模块重复使用。假设我们将在几个模块之间共享 CatsService 实例。 我们需要把 CatsService 放到 exports 数组中,如下所示:
17 |
18 | ```js
19 | // cats.module.ts
20 | import { Module } from '@nestjs/common';
21 | import { CatsController } from './cats.controller';
22 | import { CatsService } from './cats.service';
23 |
24 | @Module({
25 | controllers: [CatsController],
26 | providers: [CatsService],
27 | exports: [CatsService]
28 | })
29 | export class CatsModule {}
30 | ```
31 |
32 | 现在,每个导入 CatsModule 的模块都可以访问 CatsService ,并且它们将共享相同的 CatsService 实例。
33 |
34 | ## 全局模块
35 |
36 | 如果你不得不在任何地方导入相同的模块,那可能很烦人。在 Angular 中,提供者是在全局范围内注册的。一旦定义,他们到处可用。另一方面,Nest 将提供者封装在模块范围内。您无法在其他地方使用模块的提供者而不导入他们。但是有时候,你可能只想提供一组随时可用的东西 - 例如:helper,数据库连接等等。这就是为什么你能够使模块成为**全局模块**。
37 |
38 | ```js
39 | import { Module, Global } from '@nestjs/common';
40 | import { CatsController } from './cats.controller';
41 | import { CatsService } from './cats.service';
42 |
43 | @Global()
44 | @Module({
45 | controllers: [CatsController],
46 | providers: [CatsService],
47 | exports: [CatsService],
48 | })
49 | export class CatsModule {}
50 | ```
51 |
52 | **`@Global` 装饰器使模块成为全局作用域**。 全局模块应该只注册一次,最好由根或核心模块注册。 在上面的例子中,CatsService 组件将无处不在,而想要使用 CatsService 的模块则不需要在 imports 数组中导入 CatsModule。
53 |
54 | ## 动态模块
55 |
56 | 待...
--------------------------------------------------------------------------------
/docs/server/next/providers.md:
--------------------------------------------------------------------------------
1 | # providers
2 |
3 | > **供应商、提供者**
4 |
5 | Providers 是 `Nest` 的一个基本概念。许多基本的 Nest 类可能被视为 `provider` - `service`, `repository`, `factory`, `helper` 等等。 他们都可以通过 `constructor` 注入依赖关系。 这意味着对象可以彼此创建各种关系,并且“连接”对象实例的功能在很大程度上可以委托给 `Nest` 运行时系统。 Provider 只是一个用 `@Injectable()` 装饰器注释的类。
6 |
7 | ## Dependency injection
8 |
9 | > **依赖注入基本原理**
10 |
11 | 依赖注入是一种控制反转(IoC)技术,您可以将依赖的实例化委派给 IoC 容器(在我们的示例中为 NestJS 运行时系统),而不是必须在自己的代码中执行。 让我们从“提供者”一章中检查此示例中发生的情况。
12 |
13 | 首先,我们定义一个提供者。@Injectable()装饰器将 CatsService 类标记为提供者。
14 |
15 | ```js
16 | //cats.service.ts
17 | import { Injectable } from '@nestjs/common'
18 | import { Cat } from './interfaces/cat.interface'
19 |
20 | @Injectable()
21 | export class CatsService {
22 | private readonly cats: Cat[] = []
23 |
24 | findAll(): Cat[] {
25 | return this.cats
26 | }
27 | }
28 | ```
29 |
30 | 然后,我们要求 Nest 将提供程序注入到我们的控制器类中
31 |
32 | ```js
33 | //cats.controller.ts
34 | import { Controller, Get } from '@nestjs/common'
35 | import { CatsService } from './cats.service'
36 | import { Cat } from './interfaces/cat.interface'
37 |
38 | @Controller('cats')
39 | export class CatsController {
40 | constructor(private readonly catsService: CatsService) {}
41 |
42 | @Get()
43 | async findAll(): Promise {
44 | return this.catsService.findAll()
45 | }
46 | }
47 | ```
48 |
49 | 最后,我们在 Nest IoC 容器中注册提供程序
50 |
51 | ```js
52 | //app.module.ts
53 | import { Module } from '@nestjs/common'
54 | import { CatsController } from './cats/cats.controller'
55 | import { CatsService } from './cats/cats.service'
56 |
57 | @Module({
58 | controllers: [CatsController],
59 | providers: [CatsService],
60 | })
61 | export class AppModule {}
62 | ```
63 |
64 | 在整个过程中有三个关键的步骤:
65 |
66 | 1. 在 cats.service.ts 中 @Injectable() 装饰器声明 CatsService 类是一个可以由Nest IoC容器管理的类。
67 |
68 | 2. 在 cats.controller.ts 中 CatsController 声明了一个依赖于 CatsService 令牌(token)的构造函数注入:
69 |
70 | ```js
71 | constructor(private readonly catsService: CatsService)
72 | ```
73 |
74 | 3. 在 `app.module.ts` 文件中,我们将标识 `CatsService` 和 `cats.service.ts` 文件中的类 `CatsService` 联系起来。下面我们将看到这种关联(也叫注册)是如何发生的。
75 |
76 | * 当 Nest IoC 容器实例化 CatsController 时,它首先查找所有依赖项*。 当找到 CatsService 依赖项时,它将对 CatsService令牌(token)执行查找,并根据上述步骤(上面的#3)返回 CatsService 类。 假定单例范围(默认行为),Nest 然后将创建 CatsService 实例,将其缓存并返回,或者如果已经缓存,则返回现有实例。
77 |
78 | * 这个解释稍微简化了一点。我们忽略的一个重要方面是,分析依赖项代码的过程非常复杂,并且发生在应用程序引导期间。一个关键特性是依赖关系分析(或“创建依赖关系图”)是可传递的。 在上面的示例中,如果 CatsService 本身具有依赖项,那么那些依赖项也将得到解决。 依赖关系图确保以正确的顺序解决依赖关系-本质上是“自下而上”。 这种机制使开发人员不必管理此类复杂的依赖关系图。
79 |
80 | ## Standard providers
81 |
82 | > **标准供应商**
83 |
84 | 让我们近距离看看 `@Module()` 装饰器。在 `app.module` 中我们声明:
85 |
86 | ```js
87 | @Module({
88 | controllers: [CatsController],
89 | providers: [CatsService],
90 | })
91 | ```
92 |
93 | `providers` 属性接受一个提供者数组。到目前为止,我们已经通过一个类名列表提供了这些提供者。实际上,该语法 `providers: [CatsService]` 是更完整语法的简写
94 |
95 | ```js
96 | providers: [
97 | {
98 | provide: CatsService,
99 | useClass: CatsService,
100 | }
101 | ]
102 | ```
103 |
104 | 现在我们看到了这个显式的构造,我们可以理解注册过程。在这里,我们明确地将令牌 `CatsService` 与类 `CatsService` 关联起来。简写表示法只是为了简化最常见的用例,其中令牌用于请求同名类的实例。
105 |
106 |
107 | ## Custom providers
108 |
109 | > **自定义供应商**
110 |
111 | 当你的需求超过了那些标准供应商所提供的时候发生了什么?这有一些例子:
112 |
113 | 1. 你想创建一个自定义的实例而不是让Nest实例化(或者返回一个缓存的实例)。
114 | 2. 你想要在第二个依赖中重用一个现有的类。
115 | 3. 你想要重载一个类用于测试的模拟版本。
116 |
117 | Nest允许你定义自定义供应商来处理这些情况。它提供了几种方式来定义自定义供应商。
118 |
119 | ### 值提供者 (useValue)
120 |
121 | `useValue` 语法对于注入常量值、将外部库放入 Nest 容器或使用模拟对象替换实际实现非常有用。假设您希望强制 `Nest` 使用模拟 `CatsService` 进行测试。
122 |
123 | ```js
124 | import { CatsService } from './cats.service';
125 |
126 | const mockCatsService = {
127 | /* mock implementation
128 | ...
129 | */
130 | };
131 |
132 | @Module({
133 | imports: [CatsModule],
134 | providers: [
135 | {
136 | provide: CatsService,
137 | useValue: mockCatsService,
138 | },
139 | ],
140 | })
141 | export class AppModule {}
142 |
143 | ```
144 |
145 | 在本例中,CatsService 令牌将解析为 mockCatsService 模拟对象。useValue 需要一个值——在本例中是一个文字对象,它与要替换的 CatsService 类具有相同的接口。由于 TypeScript 的结构类型化,您可以使用任何具有兼容接口的对象,包括文本对象或用 new 实例化的类实例。
146 |
147 | 到目前为止,我们已经使用了类名作为我们的提供者标记( providers 数组中列出的提供者中的 Provide 属性的值)。 这与基于构造函数的注入所使用的标准模式相匹配,其中令牌也是类名。有时,我们可能希望灵活使用字符串或符号作为 DI 令牌。 例如:
148 |
149 | ```js
150 | import { connection } from './connection';
151 |
152 | @Module({
153 | providers: [
154 | {
155 | provide: 'CONNECTION',
156 | useValue: connection,
157 | },
158 | ],
159 | })
160 | export class AppModule {}
161 | ```
162 |
163 | 在本例中,我们将字符串值令牌 `('CONNECTION')` 与从外部文件导入的已存在的连接对象相关联。
164 |
165 | :::warning
166 | 除了使用字符串作为令牌之外,还可以使用JavaScript Symbol。
167 | :::
168 |
169 | 我们前面已经看到了如何使用基于标准构造函数的注入模式注入提供者。此模式要求用类名声明依赖项。'CONNECTION' 自定义提供程序使用字符串值令牌。让我们看看如何注入这样的提供者。为此,我们使用 @Inject() 装饰器。这个装饰器只接受一个参数——令牌。
170 |
171 | ```js
172 | @Injectable()
173 | export class CatsRepository {
174 | constructor(@Inject('CONNECTION') connection: Connection) {}
175 | }
176 | ```
177 |
178 | :::warning
179 | @Inject()装饰器是从@nestjs/common包中导入的。
180 | :::
181 |
182 | 虽然我们在上面的例子中直接使用字符串 `'CONNECTION'` 来进行说明,但是为了清晰的代码组织,最佳实践是在单独的文件(例如 `constants.ts` )中定义标记。 对待它们就像对待在其自己的文件中定义并在需要时导入的符号或枚举一样。
183 |
184 | ### 类提供者 (useClass)
185 |
186 | `useClass` 语法允许您动态确定令牌应解析为的类。 例如,假设我们有一个抽象(或默认)的 `ConfigService` 类。 根据当前环境,我们希望 `Nest` 提供配置服务的不同实现。 以下代码实现了这种策略。
187 |
188 | ```js
189 | const configServiceProvider = {
190 | provide: ConfigService,
191 | useClass:
192 | process.env.NODE_ENV === 'development'
193 | ? DevelopmentConfigService
194 | : ProductionConfigService,
195 | };
196 |
197 | @Module({
198 | providers: [configServiceProvider],
199 | })
200 | export class AppModule {}
201 | ```
202 |
203 | 让我们看一下此代码示例中的一些细节。 您会注意到,我们首先定义对象 `configServiceProvider`,然后将其传递给模块装饰器的 `providers` 属性。 这只是一些代码组织,但是在功能上等同于我们到目前为止在本章中使用的示例。
204 |
205 | 另外,我们使用 `ConfigService` 类名称作为令牌。 对于任何依赖 `ConfigService` 的类,Nest 都会注入提供的类的实例( `DevelopmentConfigService` 或 `ProductionConfigService`),该实例将覆盖在其他地方已声明的任何默认实现(例如,使用 @Injectable() 装饰器声明的 `ConfigService`)
206 |
207 | ### 工厂提供者 (useFactory)
208 |
209 | `useFactory` 语法允许动态创建提供程序。实工厂函数的返回实际的 `provider` 。工厂功能可以根据需要简单或复杂。一个简单的工厂可能不依赖于任何其他的提供者。更复杂的工厂可以自己注入它需要的其他提供者来计算结果。对于后一种情况,工厂提供程序语法有一对相关的机制:
210 |
211 | 工厂函数可以接受(可选)参数。
212 |
213 | `inject` 属性接受一个提供者数组,在实例化过程中,`Nest` 将解析该数组并将其作为参数传递给工厂函数。这两个列表应该是相关的: `Nest` 将从 `inject` 列表中以相同的顺序将实例作为参数传递给工厂函数。
214 |
215 | 下面示例演示:
216 |
217 | ```js
218 | const connectionFactory = {
219 | provide: 'CONNECTION',
220 | useFactory: (optionsProvider: OptionsProvider) => {
221 | const options = optionsProvider.get();
222 | return new DatabaseConnection(options);
223 | },
224 | inject: [OptionsProvider],
225 | };
226 |
227 | @Module({
228 | providers: [connectionFactory],
229 | })
230 | export class AppModule {}
231 | ```
232 |
233 | ### 别名提供者 (useExisting)
234 |
235 | useExisting 语法允许您为现有的提供程序创建别名。这将创建两种访问同一提供者的方法。在下面的示例中,(基于string)令牌 'AliasedLoggerService' 是(基于类的)令牌 LoggerService 的别名。假设我们有两个不同的依赖项,一个用于 'AlilasedLoggerService' ,另一个用于 LoggerService 。如果两个依赖项都用单例作用域指定,它们将解析为同一个实例。
236 |
237 | ```js
238 | @Injectable()
239 | class LoggerService {
240 | /* implementation details */
241 | }
242 |
243 | const loggerAliasProvider = {
244 | provide: 'AliasedLoggerService',
245 | useExisting: LoggerService,
246 | };
247 |
248 | @Module({
249 | providers: [LoggerService, loggerAliasProvider],
250 | })
251 | export class AppModule {}
252 | ```
253 |
254 | ### 非服务提供者
255 |
256 | 虽然提供者经常提供服务,但他们并不限于这种用途。提供者可以提供任何值。例如,提供程序可以根据当前环境提供配置对象数组,如下所示:
257 |
258 | ```js
259 | const configFactory = {
260 | provide: 'CONFIG',
261 | useFactory: () => {
262 | return process.env.NODE_ENV === 'development'
263 | ? devConfig
264 | : prodConfig;
265 | },
266 | };
267 |
268 | @Module({
269 | providers: [configFactory],
270 | })
271 | export class AppModule {}
272 | ```
273 |
274 | ### 导出自定义提供者
275 |
276 | 与任何提供程序一样,自定义提供程序的作用域仅限于其声明模块。要使它对其他模块可见,必须导出它。要导出自定义提供程序,我们可以使用其令牌或完整的提供程序对象。
277 |
278 | 以下示例显示了使用 token 的例子:
279 |
280 | ```js
281 | const connectionFactory = {
282 | provide: 'CONNECTION',
283 | useFactory: (optionsProvider: OptionsProvider) => {
284 | const options = optionsProvider.get();
285 | return new DatabaseConnection(options);
286 | },
287 | inject: [OptionsProvider],
288 | };
289 |
290 | @Module({
291 | providers: [connectionFactory],
292 | exports: ['CONNECTION'],
293 | })
294 | export class AppModule {}
295 | ```
296 |
297 | **or**
298 |
299 | ```js
300 | const connectionFactory = {
301 | provide: 'CONNECTION',
302 | useFactory: (optionsProvider: OptionsProvider) => {
303 | const options = optionsProvider.get();
304 | return new DatabaseConnection(options);
305 | },
306 | inject: [OptionsProvider],
307 | };
308 |
309 | @Module({
310 | providers: [connectionFactory],
311 | exports: [connectionFactory],
312 | })
313 | export class AppModule {}
314 | ```
--------------------------------------------------------------------------------
/docs/tools/city.md:
--------------------------------------------------------------------------------
1 | # Dwb-City
2 |
3 | []()
4 | [](https://www.npmjs.com/package/dwb-city)
5 | [](https://www.npmjs.com/package/dwb-city)
6 | [](https://github.com/dengwb1991/dwb-city)
7 |
8 | ## Install
9 |
10 | ``` bash
11 | npm i dwb-city --save
12 | ```
13 |
14 | ## Mount
15 |
16 | ### mount with global
17 |
18 | ``` javascript
19 | import Vue from 'vue'
20 | import City from 'dwb-city'
21 |
22 | Vue.use(City)
23 | ```
24 |
25 | ### mount with component
26 |
27 | ``` javascript
28 | import { dwbCity } from 'dwb-city'
29 |
30 | export default {
31 | components: {
32 | dwbCity
33 | }
34 | }
35 | ```
36 |
37 | ## Use
38 | ``` html
39 |
44 | ```
45 | ``` javascript
46 | data () {
47 | return {
48 | cityData: [CITY JSON],
49 | show: [True/False],
50 | city: [110000, 110100, 110112]
51 | }
52 | }
53 | // set Data Method or
54 | this.$refs.city.setCurr(this.city)
55 | ```
56 |
57 | ## Example
58 | ```bash
59 | npm install
60 |
61 | npm run dev
62 | ```
63 |
64 | [[Dwb City]](http://vuetool.dengwb.com/#/city)
65 |
66 | ## Author
67 | [[Dengwb]](http://www.dengwb.com/app/welcome.html)
--------------------------------------------------------------------------------
/docs/tools/drawer.md:
--------------------------------------------------------------------------------
1 | # dwb-vue-drawer
2 |
3 | []()
4 | [](https://www.npmjs.com/package/dwb-vue-drawer)
5 | [](https://www.npmjs.com/package/dwb-vue-drawer)
6 | [](https://github.com/dengwb1991/dwb-vue-drawer)
7 |
8 |
9 |
10 | ## Install
11 |
12 | ``` bash
13 | npm i dwb-vue-drawer --save
14 | ```
15 |
16 | ## Mount
17 |
18 | ### mount with global
19 |
20 | ``` javascript
21 | import Vue from 'vue'
22 | import Drawer from 'dwb-vue-drawer'
23 |
24 | Vue.use(Drawer)
25 | ```
26 |
27 | ### mount with component
28 |
29 | ``` javascript
30 | import { DwbVueDrawer } from 'dwb-vue-drawer'
31 |
32 | export default {
33 | components: {
34 | DwbVueDrawer
35 | }
36 | }
37 | ```
38 |
39 | ## Props
40 |
41 | Attribute | Type | Default | Description
42 | --- | --- | --- | ---
43 | visible | boolean | false | visibility of Drawer, supports the `.sync` modifier
44 | position | string | bottom | pop-up direction
45 | lockScroll | boolean | true | whether scroll of body is disabled while Drawer is displayed
46 | maskClosable | boolean | true | whether hide the component when clicked the mask layer
47 | zIndex | number | 100 | the value of the style z-index
48 | maskStyle | object | - | custom style of mask
49 | containerStyle | object | - | container style of container
50 |
51 | ## Events
52 |
53 | Attribute | Value | Description
54 | ---- | --- | ---
55 | open | - | drawer open callback
56 | close | - | drawer close callback
57 |
58 | ## Methods
59 |
60 | Attribute | Value | Description
61 | ---- | --- | ---
62 | open | - | open method
63 | close | - | close method
64 |
65 | ## Demo
66 |
67 | ```html
68 |
69 |
78 |
79 | - 111
80 | - 222
81 | - 333
82 |
83 |
84 |
85 |
116 | ```
117 |
118 |
119 | ## Example
120 | ```bash
121 | npm install
122 |
123 | npm run dev
124 | ```
125 |
126 | [[Dwb Vue Drawer]](http://vuetool.dengwb.com/#/drawer)
127 |
128 | ## Author
129 | [[Dengwb]](http://www.dengwb.com/app/welcome.html)
130 |
--------------------------------------------------------------------------------
/docs/tools/tabBar.md:
--------------------------------------------------------------------------------
1 | # dwb-vue-tab
2 |
3 | []()
4 | [](https://www.npmjs.com/package/dwb-vue-tab)
5 | [](https://www.npmjs.com/package/dwb-vue-tab)
6 | [](https://github.com/dengwb1991/vue-tab-bar)
7 |
8 |
9 |
10 | ## Install
11 |
12 | ``` bash
13 | npm i dwb-vue-tab --save
14 | ```
15 |
16 | ## Mount
17 |
18 | ### mount with global
19 |
20 | ``` javascript
21 | import Vue from 'vue'
22 | import TabBar from 'dwb-vue-tab'
23 |
24 | Vue.use(TabBar)
25 | ```
26 |
27 | ### mount with component
28 |
29 | ``` javascript
30 | import { DwbVueTab } from 'dwb-vue-tab'
31 |
32 | export default {
33 | components: {
34 | DwbVueTab
35 | }
36 | }
37 | ```
38 |
39 | ## Props
40 |
41 | Attribute | Type | Default | Description
42 | --- | --- | --- | ---
43 | data | array | [] | for data rendered with tab-bar
44 | active | [number,string] | 0 | the element selected by default, supports the .sync modifier
45 | tabWidth | string | '110px' | tab-bar width
46 | tabHeight | string | '40px' | tab-bar height
47 | tabColor | string | '#999' | tab-bar color
48 | tabStyle | object | {} | custom style of tab-bar
49 | fontSize | string | '14px' | font size of tab-bar
50 | fontFamily | string | 'PingFangSC-Regular' | font family of tab-bar
51 | name | [string, obejct] | null | if the data-element is an object, render data with name
52 | background | string | '#FFF' | tab-bar background
53 | highlight | string | '#A5884D' | activated the colors shown
54 | activeStyle | object | {} | activated the style shown
55 | lineUse | boolean | true | use underlining or not
56 | lineHeight | string | '2px' | underline height
57 | lineWidth | [string, object] | null | underline width
58 | lineColor | string | '#A5884D' | underline color
59 | lineStyle | object | {} | custom style of underline
60 | initCallback | boolean | false | initialize execution callback
61 |
62 | ## Events
63 |
64 | Attribute | Value | Description
65 | ---- | --- | ---
66 | callback | (activated element) | execute when switching activation elements
67 |
68 | ## Demo
69 |
70 | ```html
71 |
72 |
76 |
77 |
93 | ```
94 |
95 |
96 | ## Example
97 | ```bash
98 | npm install
99 |
100 | npm run dev
101 | ```
102 |
103 | [[Vue Tab Bar]](http://vuetool.dengwb.com/#/tab)
104 |
105 | [[More Example]](https://github.com/dengwb1991/Tool/tree/master/src/components/Tab)
106 |
107 | ## Author
108 | [[Dengwb]](http://www.dengwb.com/app/welcome.html)
109 |
--------------------------------------------------------------------------------
/docs/vue/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | home: true
3 | ---
4 |
5 | [Vue2.x实现原理](/vue/implementation-principle.html)
6 |
7 | [Vuex](/vue/vuex.html)
8 |
--------------------------------------------------------------------------------
/docs/vue/implementation-principle.md:
--------------------------------------------------------------------------------
1 | # Vue 实现原理
2 |
3 | 
4 |
5 | 1. 创建 Vue 实例对象;
6 | 2. `init`过程会初始化生命周期,初始化事件中心,初始化渲染、执行`beforeCreate`周期函数、初始化 `data`、`props`、`computed`、`watcher`、执行`created`周期函数等;
7 | 3. 初始化后,调用`$mount`方法对Vue实例进行挂载(挂载的核心过程包括模板编译、渲染以及更新三个过程)。
8 | 4. 如果没有在 Vue 实例上定义`render`方法而是定义了`template`,那么需要经历编译阶段。需要先将`template` 字符串编译成 `render function`,`template` 字符串编译步骤如下 :
9 |
10 | * `parse`正则解析template字符串形成 AST(抽象语法树,是源代码的抽象语法结构的树状表现形式)
11 | * `optimize`标记静态节点跳过 DIFF 算法(DIFF 算法是逐层进行比对,只有同层级的节点进行比对,因此时间的复杂度只有 O(n)
12 | * `generate`将 AST 转化成`render function`字符串
13 |
14 | 5. 编译成`render function`后,调用`$mount`的`mountComponent`方法,先执行`beforeMount`钩子函数,然后核心是实例化一个渲染`Watcher`,在它的回调函数(初始化的时候执行,以及组件实例中监测到数据发生变化时执行)中调用`updateComponent`方法(此方法调用`render`方法生成虚拟 Node,最终调用`update`方法更新 DOM)。
15 | 6. 调用`render`方法将`render function`渲染成虚拟的DOM(真正的 DOM 元素是非常庞大的,因为浏览器的标准就把 DOM 设计的非常复杂。如果频繁的去做 DOM 更新,会产生一定的性能问题,而 Virtual DOM 就是用一个原生的 JavaScript 对象去描述一个 DOM 节点,所以它比创建一个 DOM 的代价要小很多,而且修改属性也很轻松,还可以做到跨平台兼容),`render`方法的第一个参数是`createElement`(或者说是h函数),这个在官方文档也有说明。
16 | 7. 生成虚拟 DOM 树后,需要将虚拟 DOM 树转化成真实的 DOM 节点,此时需要调用`update`方法,`update`方法又会调用`pacth`方法把虚拟 DOM 转换成真正的 DOM 节点。需要注意在图中忽略了新建真实 DOM 的情况(如果没有旧的虚拟 DOM,那么可以直接通过`createElm`创建真实 DOM 节点),这里重点分析在已有虚拟 DOM 的情况下,会通过`sameVnode`判断当前需要更新的 DOM节点是否和旧的 DOM 节点相同(例如我们设置的`key`属性发生了变化,那么节点显然不同),如果节点不同那么将旧节点采用新节点替换即可,如果相同且存在子节点,需要调用`patchVNode`方法执行 DIFF 算法更新 DOM,从而提升 DOM 操作的性能。
17 |
18 | 响应式流程:
19 |
20 | 1. 在`init`的时候会利用`Object.defineProperty`方法(不兼容 IE8)监听Vue实例的响应式数据的变化从而实现数据劫持能力(利用了 JavaScript 对象的访问器属性`get`和`set`,在未来的 Vue3 中会使用 ES6 的Proxy来优化响应式原理)。在初始化流程中的编译阶段,当`render function`被渲染的时候,会读取Vue实例中和视图相关的响应式数据,此时会触发`getter`函数进行**依赖收集**(将观察者`Watcher`对象存放到当前闭包的订阅者`Dep`的`subs`中),此时的数据劫持功能和观察者模式就实现了一个 MVVM 模式中的 **Binder**,之后就是正常的渲染和更新流程。
21 | 2. 当数据发生变化或者视图导致的数据发生了变化时,会触发数据劫持的`setter`函数,`setter`会通知初始化**依赖收集**中的`Dep`中的和视图相应的`Watcher`,告知需要重新渲染视图,`Wather`就会再次通过`update`方法来更新视图。
--------------------------------------------------------------------------------
/docs/vue/vuex.md:
--------------------------------------------------------------------------------
1 | # vuex
2 |
3 | 
--------------------------------------------------------------------------------
/docs/webpack/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | home: true
3 | ---
4 |
5 | [编译优化-DllPlugin](/webpack/dll-plugin.html)
6 |
7 | [打包优化-NodeExternals](/webpack/webpack-node-externals.html)
8 |
9 | [自定义plugin](/webpack/custom-plugin.html)
10 |
11 | [自定义loader](/webpack/custom-loader.html)
--------------------------------------------------------------------------------
/docs/webpack/custom-loader.md:
--------------------------------------------------------------------------------
1 | # Loader
2 |
3 | :::tip
4 | 以 webpack4.x 为例
5 | :::
6 |
7 | ## Loader 的本质
8 |
9 | `Loader` 本质上是导出函数的 JavaScript 模块。所导出的函数,可用于实现内容转换。
10 |
11 | ```js
12 | /**
13 | * @param {string|Buffer} content 源文件的内容
14 | * @param {object} [map] 可以被 https://github.com/mozilla/source-map 使用的 SourceMap 数据
15 | * @param {any} [meta] meta 数据,可以是任何内容
16 | */
17 | function webpackLoader(content, map, meta) {
18 | // 你的webpack loader代码
19 | return content
20 | }
21 | module.exports = webpackLoader
22 | ```
23 |
24 | ## Normal Loader 与 Pitching Loader
25 |
26 | 模块导出的函数(若是 ES6 模块,则是默认导出的函数)就被称为 `Normal Loader`
27 |
28 | * Normal Loader 与 Webpack Loader 分类中定义的 Loader 是不一样的
29 |
30 | 在 Webpack 中,loader 可以被分为 4 类:`pre`(前置)、`post`(后置)、`normal`(普通)和`inline`(行内)。其中 `pre` 和 `post loader`,可以通过 `rule` 对象的 `enforce` 属性来指定:
31 |
32 | ```js
33 | // webpack.config.js
34 | const path = require("path");
35 |
36 | module.exports = {
37 | module: {
38 | rules: [
39 | {
40 | test: /\.txt$/i,
41 | use: ["a-loader"],
42 | enforce: "post", // post loader
43 | },
44 | {
45 | test: /\.txt$/i,
46 | use: ["b-loader"], // normal loader
47 | },
48 | {
49 | test: /\.txt$/i,
50 | use: ["c-loader"],
51 | enforce: "pre", // pre loader
52 | },
53 | ]
54 | }
55 | }
56 | ```
57 |
58 | ### Normal Loader
59 |
60 | `Loader` 模块中导出的函数成为 `Normal Loader`
61 |
62 | ```js
63 | // JS Module
64 |
65 | module.exports = function () {}
66 | ```
67 |
68 | [github Normal Loader](https://github.com/dengwb1991/lifelong-learning/blob/master/webpack/007_normal-loader/README.md)
69 |
70 | ### Pitching Loader
71 |
72 | `Loader` 模块中导出函数的 `pitch` 属性所指向的函数称为 `Pitching Loader`
73 |
74 | ```js
75 | /**
76 | * @remainingRequest 剩余请求
77 | * @precedingRequest 前置请求
78 | * @data 数据对象
79 | */
80 | function (remainingRequest, precedingRequest, data) {
81 | // some code
82 | }
83 | ```
84 |
85 | 1. 与 normal-loader 不同点是 `pitch` 执行顺序与其相反,normal 从右往左,pitch 从左往右
86 | 2. 当某个 `Pitching Loader` 返回非 undefined 值时,就会实现熔断效果,跳过剩下的loader
87 |
88 | [github Pitching Loader](https://github.com/dengwb1991/lifelong-learning/blob/master/webpack/008_pitching-loader/README.md)
89 |
90 | ### Loader 的运行过程
91 |
92 | #### loaderRunner
93 |
94 | webpack 的 NormalModule 的 doBuild 函数调用了 loaderRunder 组件的 runLoaders 去 加载loader 并且 执行loader 工厂方法
95 |
96 | #### 过程
97 |
98 | 1. 调用runLoader 执行初始化
99 | 2. iteratePitchingLoaders 遍历数组
100 | 3. loadLoader 加载loader,通过require() 方式加载。 并且把加载的工厂方法挂载到 loaderContext.loader 的 normal 中
101 | 4. 如果loader 包含pitch 函数,则立即执行
102 | 5. 如果是最后一个loader, 调用processResource 加载源码
103 | 6. 否则加载下一个loader
104 | 7. 调用processResource加载待编译的源代码 并将源码传递给 iterateNormalLoaders 执行
105 | 8. iterateNormalLoaders 调用 runSyncOrAsync
106 | 9. runSyncOrAsync 调用 loader 的工厂函数,获得编译后的结果
107 |
108 |
109 | ## 同步loader与异步loader
110 |
111 | Loader 可以分为同步 Loader 和异步 Loader
112 |
113 | * 同步:可以通过 `return` 语句或 `this.callback` 的方式来同步地返回转换后的结果。只是相比 `return` 语句,`this.callback` 方法则更灵活,因为它允许传递多个参数。
114 | * 异步:可以通过 this.async 方法来获取 callback 函数
115 |
116 | ### 同步loader demo
117 |
118 | ```js
119 | // sync-loader.js
120 | module.exports = function(source) {
121 | return source + '-simple'
122 | };
123 | ```
124 |
125 | ```js
126 | // sync-loader-with-multiple-results.js
127 | module.exports = function (source, map, meta) {
128 | this.callback(null, source + '-simple', map, meta)
129 | return // 当调用 callback() 函数时,总是返回 undefined
130 | };
131 | ```
132 |
133 | `this.callback` 方法支持 4 个参数,每个参数的具体作用如下所示:
134 |
135 | ```ts
136 | interface Callback {
137 | err: Error | null, // 错误信息
138 | content: string | Buffer, // content信息
139 | sourceMap?: SourceMap, // sourceMap
140 | meta?: any // 会被 webpack 忽略,可以是任何东西
141 | }
142 | ```
143 |
144 | ### 异步loader demo
145 |
146 | ```js
147 | // async-loader.js
148 | module.exports = function(source) {
149 | var callback = this.async();
150 | setTimeout(function() {
151 | callback(null, source + '-async-simple')
152 | }, 50)
153 | }
154 | ```
155 |
156 | ## 外链
157 |
158 | [多图详解,一次性搞懂Webpack Loader](https://juejin.cn/post/6992754161221632030)
--------------------------------------------------------------------------------
/docs/webpack/dll-plugin.md:
--------------------------------------------------------------------------------
1 | # DllPlugin
2 |
3 | `DLLPlugin` 和 `DLLReferencePlugin` 提供了一种可以显著提高构建时性能的方式来拆分包的方法。
4 |
5 | ## DllPlugin
6 |
7 | 这个插件是在一个额外的独立的 `webpack` 设置中创建一个只有 `dll` 的 bundle(dll-only-bundle)。 这个插件会生成一个名为 `manifest.json` 的文件,这个文件是用来让 `DLLReferencePlugin` 映射到相关的依赖上去的。
8 |
9 | * context (optional): manifest 文件中请求的上下文(context)(默认值为 webpack 的上下文(context))
10 | * name: 暴露出的 DLL 的函数名 (TemplatePaths: [hash] & [name] )
11 | * path: manifest.json 文件的绝对路径 (输出文件)
12 |
13 | ```js
14 | new webpack.DllPlugin(options)
15 | ```
16 |
17 | 在给定的 `path` 路径下创建一个名为 `manifest.json` 的文件。 这个文件包含了从 require 和 import 的request到模块 id 的映射。 DLLReferencePlugin 也会用到这个文件。
18 |
19 | 这个插件与 output.library 的选项相结合可以暴露出 (也叫做放入全局域) dll 函数。
20 |
21 | ## DllReferencePlugin
22 |
23 | 个插件是在 webpack 主配置文件中设置,把只有 dll 的 bundle (dll-only-bundle(s)) 引用到需要的预编译的依赖。
24 |
25 | * context: (绝对路径) manifest (或者是内容属性)中请求的上下文
26 | * manifest: 包含 content 和 name 的对象,或者在编译时(compilation)的一个用于加载的 JSON manifest 绝对路径
27 | * content (optional): 请求到模块 id 的映射 (默认值为 manifest.content)
28 | * name (optional): dll 暴露的地方的名称 (默认值为 manifest.name) (可参考 externals)
29 | * scope (optional): dll 中内容的前缀
30 | * sourceType (optional): dll 是如何暴露的 (libraryTarget)
31 |
32 | ```js
33 | new webpack.DllReferencePlugin(options)
34 | ```
35 |
36 | 通过引用 dll 的 manifest 文件来把依赖的名称映射到模块的 id 上,之后再在需要的时候通过内置的 __webpack_require__ 函数来 require 他们
37 |
38 | 与 output.library 保持 name 的一致性。
39 |
40 | ## 使用
41 |
42 | 5步配置 DllPlugin
43 |
44 | ### 第一步
45 |
46 | 创建 build 文件
47 |
48 | ```js
49 | // build/webpack.dll.config.js
50 |
51 | var path = require("path");
52 | var webpack = require("webpack");
53 |
54 | module.exports = {
55 | // 要打包的模块的数组
56 | entry: {
57 | vendor: ['vue/dist/vue.esm.js','vue-router']
58 | },
59 | output: {
60 | path: path.join(__dirname, '../static/js'), // 打包后文件输出的位置
61 | filename: '[name].dll.js',// vendor.dll.js中暴露出的全局变量名。
62 | library: '[name]_library' // 与webpack.DllPlugin中的`name: '[name]_library',`保持一致。
63 | },
64 | plugins: [
65 | new webpack.DllPlugin({
66 | path: path.join(__dirname, '.', '[name]-manifest.json'),
67 | name: '[name]_library',
68 | context: __dirname
69 | }),
70 | ]
71 | };
72 | ```
73 |
74 | ### 第二步
75 |
76 | 在 package.json 的 scripts 里添加命令
77 |
78 | ```js
79 | "dll": "webpack --config build/webpack.dll.config.js"
80 | ```
81 |
82 | ### 第三步
83 |
84 | 执行命令,生成 `manifest.json` 和 `vendor.dll.js` 文件。
85 |
86 | ```bash
87 | $ npm run dll
88 | ```
89 |
90 | ### 第四步
91 |
92 | 在 webpack.base.conf.js 添加上 DllReferencePlugin
93 |
94 | ```js
95 | // build/webpack.base.config.js
96 | plugins: [
97 | new webpack.DllReferencePlugin({
98 | context: __dirname,
99 | manifest: require('./vendor-manifest.json')
100 | })
101 | ],
102 | ```
103 |
104 | ### 第五步
105 |
106 | 在 index.html 中引入 vendor.dll.js
107 |
108 | ```html
109 |
110 |
111 |
112 |
113 | ```
114 |
115 | ## 外链
116 |
117 | [webpack plugins dll-plugin](https://webpack.js.org/plugins/dll-plugin/)
--------------------------------------------------------------------------------
/docs/webpack/webpack-node-externals.md:
--------------------------------------------------------------------------------
1 | # webpack-node-externals
2 |
3 | 排除不必要的依赖,如对vue组件进行编译打包时,排除对vue源码的依赖.
4 |
5 | [npm link](https://www.npmjs.com/package/webpack-node-externals)
6 |
7 | ## 安装使用
8 |
9 | ```bash
10 | $ npm install webpack-node-externals --save-dev
11 | ```
12 |
13 | webpack.config.js
14 |
15 | ```js
16 | var nodeExternals = require('webpack-node-externals');
17 | ...
18 | module.exports = {
19 | ...
20 | target: 'node', // in order to ignore built-in modules like path, fs, etc.
21 | externals: [nodeExternals()], // in order to ignore all modules in node_modules folder
22 | ...
23 | };
24 | ```
25 |
--------------------------------------------------------------------------------
/docs/zh/guide/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | home: true
3 | ---
4 |
5 | 该站主要目的为展示相关示例和部分总结。
6 |
7 | 持续建设...
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-press-notes",
3 | "version": "1.0.0",
4 | "description": "This is a great notes",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "vuepress dev docs",
8 | "build": "vuepress build docs",
9 | "test": "echo \"Error: no test specified\" && exit 1"
10 | },
11 | "keywords": [],
12 | "author": "dengwb",
13 | "license": "MIT",
14 | "devDependencies": {
15 | "vuepress": "^1.0.2",
16 | "vuepress-plugin-demo-code": "^0.3.5"
17 | },
18 | "dependencies": {}
19 | }
20 |
--------------------------------------------------------------------------------