(Part 2) A short story of imitating the template of the series ___________ of `Vue Ecology'.
This mission
- Continue: Complete the first unfinished'Hot Update'configuration.
- Core: Complete the relevant compilation of'template analysis'module, many articles on template analysis are too shallow, this time we will discuss in depth, as much as possible to identify user statements.
- Enlightenment: Structurally, it lays the foundation for the compilation of modules such as `bidirectional binding', watch, dep, etc.
Final effect
I. Template Pages
Since we are going to develop an mvvm, of course, we need to simulate the real usage scenario. The relevant files are placed under the'cc_vue/use'path, and the code is as follows:
- 'cc_vue/use/1: Template parsing/index.html', this article is dedicated to showing the page of template parsing.
- 'cc_vue/use/1: Template parsing/index.js', this article is dedicated to showing the logic code of template parsing.
It was intended to show the information of html files, but the content was long and there was no technology to speak of, so it was not shown here.
function init(){ new C({ el: '#app', data: { title: 'Study hard', ary: [1, 2, 3], obj: { name: 'Golden hair', type: ['Childhood', 'mature period', 'Complete body'] }, fn() { return 'Hello everyone, I am: ' + this.obj.name; } } }); } export default init;
Configuration Files and Simple Hot Updates
The reason why it is simple is that we will not do it very carefully. For example, this time we will not pursue every precise update, but every time we will update the whole. After all, this project hot update is only a knowledge point. We still have a lot of more important things to do emmmm.
Some of my own views
Hot update is not magical. I configure vuex hotupdate before. Later, I summarized that it is similar to the concept of callback function. The principle is to execute a callback function when the editor or serve r detects that your file is contingent. This callback function contains some re-rendering, more importantly. New dom and other operations, you may have doubts, Vue hot update is so good, also did not see any hotupdate callback function ah, in fact, thanks to'vue-loader', css hot update exam is css-loader, they in the processing stage of the file hot update callback code into the js file, so we only It will be senseless, so without'loader'to help us inject hot updates, we will do it manually this time.
Interested students can go to the official website of the course, a little short.
Configuration files are divided into production environment and development environment (although we can't use production, but it's good for learning).
- build - Production Packaging
- common - Public Packaging
- dev -- Development Packaging
common.js
const dev = require('./dev'); const path = require('path'); const build = require('./build'); const merge = require('webpack-merge'); const common = { entry: { main: './src/index.js' }, output: { filename: '[name].js', path: path.resolve(__dirname, '../dist') }, module: { rules: [ { test: /.css$/, use: ['style-loader', 'css-loader', 'postcss-loader'] }, { test: /.js$/, exclude: /node_modules/, loader: 'babel-loader?cacheDirectory=true' } ] } }; module.exports = env => { let config = env == 'dev' ? dev : build; return merge(common, config); };
merge of Knowledge Points
As all the students who configure webpack know, merge is a soul figure. Basically, there are many kinds of packages for every project, and configuration files are countless. It's not easy to integrate these configurations. Let's see the effect of webpack-merge.
npm i webpack-merge -D
We did the following experiments
let obj1 = { a: 1, b: [1], c: { name: 1 } }; let obj2 = { a: 2, b: [2], c: { age: 2 } }; merge(obj1, obj2) //The results are as follows:{ a: 2, b: [1, 2], c: { name: 1, age: 2 } };
- Strategically, the latter covers the former.
- When common values are encountered, they are directly overwritten.
- When you encounter an array, push() is used.
- When encountering an object, it will choose to override the original attributes, and add new attributes if there are no original attributes.
- Actually, it's interesting. I can use this method in other places, not confined to the web pack configuration, but the happiest way to learn and use it.
Explain the use of this time.
// Derived a function, only the function can receive the passed parameters. module.exports = env => { // The type of parameters is judged directly to decide which configuration to use. let config = env == 'dev' ? dev : build; // Integrate allocation with public infrastructure allocation and derive it return merge(common, config); };
Adjustment of Start Command
- Hot starts hot updates, which can be written or not in the matching file.
- --env dev To configure the incoming parameter string'dev', you must write -- env.
- Config. / config/common.js specifies that the configuration file to be invoked is. / config/common.js rather than webpack.config.js.
"serve": "webpack-dev-server --hot --env dev --config ./config/common.js",
Use of Hot Update
cc_vue/config/dev.js
const path = require('path'); const Webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { mode: 'development', devtool: 'source-map', devServer: { port: 9999, hot: true, // It must be opened here. // The difference between hot and hot only is that when some modules do not support hot updates, the former automatically refreshes the page, while the latter does not refresh the page, but fails to output hot updates in the console. }, plugins: [ // Related plug-ins also need to be loaded new Webpack.HotModuleReplacementPlugin(), new HtmlWebpackPlugin({ filename: 'index.html', template: path.resolve(__dirname, '../use/1:Template parsing/index.html'), }) ] };
Next, write the code, and we'll use the hot update module.
II. Writing Core Class C
cc_vue/src/index.js
import '../public/css/index.css'; import Compiler from './Compiler.js'; // In fact, the role of this class is very single. // In the source code of vue, this class only determines whether the user uses new or not, and also calls init. class C { constructor(options) { // Here, regardless of what the user is passing, we hang it all directly on ourselves, adding a symbol of $to indicate it. for (let key in options) { this['$' + key] = options[key]; } // We instantiate the template class here. // That is the theme of this time, template correlation analysis new Compiler(this.$el, this); } } // Compared with the traditional way of writing, hanging it on the whole, in fact, all operations only expose this variable to users. window.C = C;
Introducing hot updates is just an example. It won't be used this time.
import init from '../use/1:Template parsing/index'; ... window.C = C; // Perform initialization, the code has been glued. init(); // When there is thermal update if (module.hot) { // Monitor the changes in this file module.hot.accept('../use/1:Template parsing/index.js', function() { // What to do after the change init(); }); }
Why not use it this time?
The reason is that the project has just started, there is no update method, even if the file changes are detected, it is useless, such as user input {a}, he has been converted to a corresponding variable, at this time to update this value need to trigger his own updater method, but we have not written this method, so this time we do not use it directly. It's OK to use the refresh update, and then update a specific module.
3. Template parsing module
cc_vue/src/Compiler.js
// Analytical Template Series Programs class Compiler { // When you don't give # app, let me give you a default # app. // Because this project does not involve,'delayed mounting'so you can write like this. // vue involves delayed mounting constructor(el = '#app', vm) { this.vm = vm; // 1: If you pass dom, use it directly // Get it if it's a string this.el = this.isElementNode(el) ? el : document.querySelector(el); // 2: Document fragmentation let fragment = this.node2fragment(this.el); // 3: Parse elements, and document streams are objects // The core code for compile parsing is described below. this.compile(fragment); // The last step: After processing, stuff it back. this.el.appendChild(fragment); } /** * @method Determine whether element nodes are * @param { node } Nodes to be judged * @return { boolean } Whether it is an element node, element node 1 */ isElementNode(node) { return node.nodeType === 1; } /** * @method Determine whether a text node is * @param { node } Nodes to be judged * @return { boolean } Whether it is a label node or not, the text node is 3 */ isTextNode(node) { return node.nodeType === 3; } /** * @method Put all the nodes in the document stream * @param { node } Node objects you want to traverse * @return { fragment } Returns the generated document stream */ node2fragment(node) { // Create a document stream let fragment = document.createDocumentFragment(); while (node.firstChild) { // Insert Document Stream fragment.appendChild(node.firstChild); } return fragment; } } export default Compiler;
Compoile is the most important method. Let's talk about it next.
Dead Knowledgeable Points
- Why is the example called'vm', taken from the official website of Vue (although Vue does not fully follow the MVVM model, but the design of Vue is also inspired by it. Therefore, VM (abbreviated as ViewModel) is often used as a variable name to represent Vue instances in documents.
- NoeType, each node has its own type.
// Element nodes, 1
// Attribute node, 2
// Text node, 3
// Annotation node, 8 is also useful
// document, 9
// DocumentFragment 11 - CreateDocument Fragment () method, which I usually call creating document stream fragments, is known to be expensive to operate on dom. If you need to insert 10,000 dom, it's really gg, but the fragment problem can be solved easily. It's virtual, but it can operate like an element, so you can put the element first. Inside him, and then put it in the target parent, so that with only one insert, he will not form elements on the page.
- AppndChild and Appnd
AppndChild on Node.prototype
append on Document.prototype
Both represent the last bit added to the element
Neither incoming string generation tags, such as'< li > 1 </li >', are supported.
append() can pass in multiple nodes or strings at the same time without returning a value.
AppndChild () can only pass one node, and the passing string will cause an error unless it passes in document.createTextElement('string').
IV. Handling curly braces
vue processing is very complex, this exercise I would like to exercise their own thinking, so choose their own way to do;
... compile(node) { let childNodes = node.childNodes; [...childNodes].map(child => { if (this.isElementNode(child)) { // Element Node Processing Instructions this.compileElement(child); this.compile(child); } else if (this.isTextNode(child)) { // Text nodes handle things like {}} this.compileText(child); } }); }
We only deal with text nodes in this article, followed by the idea of compileText
/** * @method Processing text nodes * @param { node } Node objects you want to traverse */ compileText(node) { let content = node.textContent; // If you have curly braces, you'll have to deal with them. if (/\{\{.+?\}\}/.test(content)) { CompileUtil.text(node, content, this.vm); } }
Because there are many tool classes, we opened a single file.
cc_vue/src/CompileUtil.js
CompileUtil.text
const CompileUtil = { text(node, expr, vm) { let content = expr.replace(/\{\{(.+?)\}\}/g, ($0, $1) => { // The matched contents in each curly bracket are given to this function to get the value. return this.getVal(vm, $1); }); this.updater.textUpdater(node, content); } //... updater: { textUpdater(node, value) { node.textContent = value; } } }; export default CompileUtil;
Fifth, get the corresponding value (the most interesting one is him).
The getVal function is the most interesting one. I've seen many versions, and finally I'll introduce the version I've developed... You all laughed.
Idea 1, the most common online (only one case, only one layer of
Only {a.b} or {a}} can be processed.
// An example getVal(){ let str = 'a.c'; let data ={ a:{ c:2 } } // Simply split according to'. ' str = str.split('.'); let result = data; str.map(item=>{ // Take the value every time. result = result[item] }) return result }
The above method is to deal with things casually without thinking about it.
The value of a['b'] cannot be obtained if the operation a['b']+a['c'] appears in it, let alone encounter a function.
After reading many articles, I saw the way of thinking 2.
The second way is to deal with two ways of value selection and to deal with operation symbols separately.
getVal(){ // It's rather tedious. Let me talk about the train of thought. After all, I didn't write it. }
- Use pointers to sort out each character of the string one by one.
- If you encounter'[', take it out separately and carry out the operation of'Idea One', until you encounter'].
- When encountering numbers, special processing should be done, such as a[1]. It is impossible to process them into a[data.1].
- If you encounter''''or''''', you need to do special processing a['b'], which B cannot be processed as data.b.
- Repeat the above operation repeatedly, convert the original string into the form of'. 'link, and select the value in the way of thinking one.
I don't know how to think about it. At first glance, it's not the right way.
The code is too cumbersome and too judgmental, but it doesn't cover all the cases.
It belongs to the iron and silly writing, but at least it can be seen that the people who do it use their brains.
Idea 3, eval
Actually, at first I wanted to use with keyword, but I didn't use it.
- Performance is too bad, 20-30 times slower than normal code.
- Strict mode is not allowed to be used
Dead knowledge points with
let obj = {} obj.a=2; obj.b=3
let obj = {} with(obj){ a=2; b=3; }
The idea is that the statement written by the user in {}} does not write this, but points to this, so I want to prefix this to them in the head.
- Loop out variables, stitch this in the head, this is too ____________.
- Create an environment in which all variables are on this.
That is, I take the value of this. $data (which will change after a few more articles) into my current environment.
For example, this.a = 1, then I call a directly from other places in the vara = this.a function, which is equivalent to calling this.a.
getVal(vm, expression) { let result, __whoToVar = ''; // Each attribute on circular data for (let i in vm.$data) { // The next article of data will be a proxy, and the attributes on the prototype will be removed. let item = vm.$data[i]; // Functions are special because you need to change the way you declare them // Compatible Reference Transfer // Modify this to point to vm instance if (typeof item === 'function') { __whoToVar += `function ${i}(...arg){return vm.$data['${i}'].call(vm.$data,...arg)}`; } else { // Ordinary variable direct let __whoToVar += `let ${i}=vm.$data['${i}'];`; } } // The result of execution is fetched with result __whoToVar = `${__whoToVar}result=${expression}`; eval(__whoToVar); return result; },
By doing the above, you can achieve the purpose shown in the first effect chart.
end
Writing articles is too laggy, and later we have to control the number of words, eeee.
The next chapter goes into more interesting, two-way binding writing, which I like very much.
Everyone can communicate with each other, learn together, make progress together and realize self-worth as soon as possible!!
github:github
Personal Technology Blog: Personal Technology Blog
More articles, writing ui Libraries List of articles