Compiler 和 Compilation 对象

Compiler 和 Compilation 对象

chunk
[ { id: 0,
   //chunk id
    rendered: true,
    //https://github.com/liangklfangl/commonchunkplugin-source-code
    initial: false,
    //require.ensure 产生的 chunk,非 initial
    //initial 表示是否在页面初始化就需要加载的模块,而不是按需加载的模块
    entry: false,
    //是否含有 Webpack 的 runtime 环境,通过 CommonChunkPlugin 处理后,runtime 环境被提到最高层级的 chunk
    recorded: undefined,
    extraAsync: false,
    size: 296855,
    //chunk 大小、比特
    names: [],
    //require.ensure 不是通过 Webpack 配置的,所以 chunk 的 names 是空
    files: [ '0.bundle.js' ],
    //该 chunk 产生的输出文件,即输出到特定文件路径下的文件名称
    hash: '42fbfbea594ba593e76a',
    //chunk 的 hash,即 chunkHash
    parents: [ 2 ],
    //父级 chunk 的 id 值
    origins: [ [Object] ] 
    //该 chunk 是如何产生的
    },
  { id: 1,
    rendered: true,
    initial: false,
    entry: false,
    recorded: undefined,
    extraAsync: false,
    size: 297181,
    names: [],
    files: [ '1.bundle.js' ],
    hash: '456d05301e4adca16986',
    parents: [ 2 ],
    origins: [ [Object] ] }
    ```
     chunk -- origins  -- 描述了某一个 chunk 是如何产生的:
     ```
     {
  "loc": "", // Lines of code that generated this chunk
  "module": "(webpack)\\test\\browsertest\\lib\\index.web.js", // Path to the module
  "moduleId": 0, // The ID of the module
  "moduleIdentifier": "(webpack)\\test\\browsertest\\lib\\index.web.js", // Path to the module
  "moduleName": "./lib/index.web.js", // Relative path to the module
  "name": "main", // The name of the chunk
  "reasons": [
    // A list of the same `reasons` found in module objects
  ]
}
assets
[ {
  "chunkNames": [], 
  // The chunks this asset contains
  //这个输出资源包含的 chunks 名称。对于图片的 require 或者 require.ensure 动态产生的 chunk 是不会有 chunkNames 的,但是在 entry 中配置的都是会有的
  "chunks": [ 10, 6 ],
   // The chunk IDs this asset contains
   //这个输出资源包含的 chunk的ID。通过 require.ensure 产生的 chunk 或者 entry 配置的文件都会有该 chunks 数组,require 图片不会有
  "emitted": true,
   // Indicates whether or not the asset made it to the `output` directory
   //使用这个属性标识 assets 是否应该输出到 output 文件夹
  "name": "10.web.js", 
  // The `output` filename
  //表示输出的文件名
  "size": 1058 
  // The size of the file in bytes
  //输出的这个资源的文件大小
}
  { name: '1.bundle.js',
    size: 299469,
    chunks: [ 1, 3 ],
    chunkNames: [],
    emitted: undefined,
    isOverSizeLimit: undefined },
  { name: 'bundle.js',
    
    size: 968,
    
    chunks: [ 2, 3 ],
    
    chunkNames: [ 'main' ],
    
    emitted: undefined,
    
    isOverSizeLimit: undefined },
  { name: 'vendor.bundle.js',
    size: 5562,
    chunks: [ 3 ],
    chunkNames: [ 'vendor' ],
    emitted: undefined,
    isOverSizeLimit: undefined }]
modules
{ id: 10,
//该模块的 id 和 `module.id` 一样
identifier: 'C:\\Users\\Administrator\\Desktop\\webpack-chunkfilename\\node_
odules\\html-loader\\index.js!C:\\Users\\Administrator\\Desktop\\webpack-chunkf
lename\\src\\Components\\Header.html',
//Webpack 内部使用这个唯一的 ID 来表示这个模块
name: './src/Components/Header.html',
//模块名称,已经转化为相对于根目录的路径
index: 10,
index2: 8,
size: 62,
cacheable: true,
//表示这个模块是否可以缓存,调用 this.cacheable()
built: true,
//表示这个模块通过 Loader、Parsing、Code Generation 阶段
optional: false,
//所以对该模块的加载全部通过 try..catch 包裹
prefetched: false,
//表示该模块是否是预加载的。即在第一个 import、require 调用之前就开始解析和打包该模块https://webpack.js.org/plugins/prefetch-plugin/
chunks: [ 0 ],
//该模块在那个 chunk 中出现
assets: [],
//该模块包含的所有的资源文件集合
issuer: 'C:\\Users\\Administrator\\Desktop\\webpack-chunkfilename\\node_modu
es\\eslint-loader\\index.js!C:\\Users\\Administrator\\Desktop\\webpack-chunkfil
name\\src\\Components\\Header.js',
//是谁开始本模块的调用的,即模块调用发起者
issuerId: 1,
//发起者的 moduleid
issuerName: './src/Components/Header.js',
//发起者相对于根目录的路径
profile: undefined,
failed: false,
//在解析或者处理该模块的时候是否失败
errors: 0,
//在解析或者处理该模块的是否出现的错误数量
warnings: 0,
//在解析或者处理该模块的是否出现的警告数量
reasons: [ [Object] ],
usedExports: [ 'default' ],
providedExports: null,
depth: 2,
source: 'module.exports = "<header class=\\"header\\">{{text}}</header>";' }
//source 是模块内容,但是已经变成了字符串了
{
"loc": "33:24-93",
// Lines of code that caused the module to be included
"module": "./lib/index.web.js",
// Relative path to the module based on context
"moduleId": 0, 
// The ID of the module
"moduleIdentifier": "(webpack)\\test\\browsertest\\lib\\index.web.js", 
// Path to the module
"moduleName": "./lib/index.web.js", 
// A more readable name for the module (used for "pretty-printing")
"type": "require.context", 
// The type of request used
"userRequest": "../../cases" 
// Raw string used for the `import` or `require` request
}

Compiler 对象

Webpack 的 Compiler 模块是 Webpack 主要引擎,通过它可以创建一个 Compilation 实例,而且所有通过 cli 或者 Webpack 的 API 或者 Webpack 的配置文件传入的配置都会作为参数来构建一个 Compilation 实例。可以通过 webpack.compiler 来访问它。Webpack 通过实例化一个 Compiler 对象,然后调用它的 run 方法来开始一次完整的编译过程。

watch

Compiler.prototype.watch = function(watchOptions, handler) {
  this.fileTimestamps = {};
  this.contextTimestamps = {};
  var watching = new Watching(this, watchOptions, handler);
  return watching;
};
if (args.watch) {
    compiler.watch(args.watch || 200, doneHandler);
  } else {
    compiler.run(doneHandler);
  }

Parser对象

function Compiler() {
  Tapable.call(this);
  this.parser = {
    plugin: function(hook, fn) {
      this.plugin("compilation", function(compilation, data) {
        data.normalModuleFactory.plugin("parser", function(parser) {
          parser.plugin(hook, fn);
        });
      });
    }.bind(this),
    apply: function() {
      this.plugin("compilation", function(compilation, data) {
        data.normalModuleFactory.plugin("parser", function(parser) {
          parser.apply.apply(parser, args);
        });
      });
    }.bind(this)
  };

  this.options = {};
}

Compiler会先继承了Tapable;在parser.plugin中注入的回调函数。
compiler.run

Compiler.prototype.run = function(callback) {
  var self = this;
  var startTime = new Date().getTime();
   //before run
  self.applyPluginsAsync("before-run", self, function(err) {
    if(err) return callback(err);
        //run
    self.applyPluginsAsync("run", self, function(err) {
      if(err) return callback(err);
      self.readRecords(function(err) {
        if(err) return callback(err);
         //compile函数被调用,我们传入run函数的回调函数会在compile回调函数中调用
        //也就是在compiler的'done'之后回调
        self.compile(function onCompiled(err, compilation) {
          if(err) return callback(err);
          if(self.applyPluginsBailResult("should-emit", compilation) === false) {
            var stats = compilation.getStats();
            stats.startTime = startTime;
            stats.endTime = new Date().getTime();
            self.applyPlugins("done", stats);
            return callback(null, stats);
          }
          self.emitAssets(compilation, function(err) {
            if(err) return callback(err);
            if(compilation.applyPluginsBailResult("need-additional-pass")) {
              compilation.needAdditionalPass = true;
              var stats = compilation.getStats();
              stats.startTime = startTime;
              stats.endTime = new Date().getTime();
              self.applyPlugins("done", stats);
              self.applyPluginsAsync("additional-pass", function(err) {
                if(err) return callback(err);
                self.compile(onCompiled);
              });
              return;
            }
            self.emitRecords(function(err) {
              if(err) return callback(err);
              var stats = compilation.getStats();
              stats.startTime = startTime;
              stats.endTime = new Date().getTime();
              self.applyPlugins("done", stats);
              return callback(null, stats);//调用'done'
            });
          });
        });
      });
    });
  });
};

compiler.compile方法运行结束后会进行相应的回调,其中回调函数就是我们通过compile.run调用时候传入的函数
其中我们要注意我们传入的callback会被传入一个参数,这个参数是通过如下方式来获取到的:

 var stats = compilation.getStats();
     stats.startTime = startTime;
     stats.endTime = new Date().getTime();

那么getStats到底得到的是什么呢?

getStats() {
    return new Stats(this);
  }

也就是说我们得到的是一个Stats对象,具体用法看参考文献。那么我们给出一个例子:

function doneHandler(err, stats) {
   if (args.json) {
     const filename = typeof args.json === 'boolean' ? 'build-bundle.json' : args.json;
     const jsonPath = join(fileOutputPath, filename);
     writeFileSync(jsonPath, JSON.stringify(stats.toJson()), 'utf-8');
     console.log(`Generate Json File: ${jsonPath}`);
   }
   //如果出错,那么退出码是1
   const { errors } = stats.toJson();
   if (errors && errors.length) {
     process.on('exit', () => {
       process.exit(1);
     });
   }
   // if watch enabled only stats.hasErrors would log info
   // otherwise  would always log info
   if (!args.watch || stats.hasErrors()) {
     const buildInfo = stats.toString({
       colors: true,
       children: true,
       chunks: !!args.verbose,
       modules: !!args.verbose,
       chunkModules: !!args.verbose,
       hash: !!args.verbose,
       version: !!args.verbose,
     });
     if (stats.hasErrors()) {
       console.error(buildInfo);
     } else {
       console.log(buildInfo);
     }
   }
   if (err) {
     process.on('exit', () => {
       process.exit(1);
     });
     console.error(err);
   }
   if (callback) {
     callback(err);
   }
 }

主要的代码就是调用stats.toJson方法,内容就是获取本次编译的主要信息。同时参考文献中也给出了一个输出的例子,可以自己查看。
我们自己的回调函数是在compiler的'done'回调以后触发的,而且和compiler的'done'回调一样,我们也是也是给我们的函数传入err和Stats对象!

Compiler.prototype.compile = function(callback) {
self.applyPluginsAsync("before-compile", params, function(err) {
  self.applyPlugins("compile", params);
  var compilation = self.newCompilation(params);
  //调用compiler的compile方法,我们才会构建出一个Compilation实例对象,在
  //'make'钩子里面我们就可以获取到compilation对象了
  self.applyPluginsParallel("make", compilation, function(err) {
    compilation.finish();
    compilation.seal(function(err) {
      self.applyPluginsAsync("after-compile", compilation, function(err) {
        //在compilation.seal方法调用以后我们才会执行'after-compile'
      });
    });
  });
});
};

compilation的finish方法:

finish() {
      this.applyPlugins1("finish-modules", this.modules);
      this.modules.forEach(m => this.reportDependencyErrorsAndWarnings(m, [m]));
    }

compilation.seal方法:

seal(callback) {
  self.applyPlugins0("seal");
  self.applyPlugins0("optimize");
  while(self.applyPluginsBailResult1("optimize-modules-basic", self.modules) ||
    self.applyPluginsBailResult1("optimize-modules", self.modules) ||
    self.applyPluginsBailResult1("optimize-modules-advanced", self.modules));
  self.applyPlugins1("after-optimize-modules", self.modules);
  //这里是optimize module
  while(self.applyPluginsBailResult1("optimize-chunks-basic", self.chunks) ||
    self.applyPluginsBailResult1("optimize-chunks", self.chunks) ||
    self.applyPluginsBailResult1("optimize-chunks-advanced", self.chunks));
    //这里是optimize chunk
  self.applyPlugins1("after-optimize-chunks", self.chunks);
  //这里是optimize tree
  self.applyPluginsAsyncSeries("optimize-tree", self.chunks, self.modules, function sealPart2(err) {
    self.applyPlugins2("after-optimize-tree", self.chunks, self.modules);
    const shouldRecord = self.applyPluginsBailResult("should-record") !== false;
    self.applyPlugins2("revive-modules", self.modules, self.records);
    self.applyPlugins1("optimize-module-order", self.modules);
    self.applyPlugins1("advanced-optimize-module-order", self.modules);
    self.applyPlugins1("before-module-ids", self.modules);
    self.applyPlugins1("module-ids", self.modules);
    self.applyModuleIds();
    self.applyPlugins1("optimize-module-ids", self.modules);
    self.applyPlugins1("after-optimize-module-ids", self.modules);
    self.sortItemsWithModuleIds();
    self.applyPlugins2("revive-chunks", self.chunks, self.records);
    self.applyPlugins1("optimize-chunk-order", self.chunks);
    self.applyPlugins1("before-chunk-ids", self.chunks);
    self.applyChunkIds();
    self.applyPlugins1("optimize-chunk-ids", self.chunks);
    self.applyPlugins1("after-optimize-chunk-ids", self.chunks);
    self.sortItemsWithChunkIds();
    if(shouldRecord)
      self.applyPlugins2("record-modules", self.modules, self.records);
    if(shouldRecord)
      self.applyPlugins2("record-chunks", self.chunks, self.records);
    self.applyPlugins0("before-hash");
    self.createHash();
    self.applyPlugins0("after-hash");
    if(shouldRecord)
      self.applyPlugins1("record-hash", self.records);
    self.applyPlugins0("before-module-assets");
    self.createModuleAssets();
    if(self.applyPluginsBailResult("should-generate-chunk-assets") !== false) {
      self.applyPlugins0("before-chunk-assets");
      self.createChunkAssets();
    }
    self.applyPlugins1("additional-chunk-assets", self.chunks);
    self.summarizeDependencies();
    if(shouldRecord)
      self.applyPlugins2("record", self, self.records);

    self.applyPluginsAsync("additional-assets", err => {
      if(err) {
        return callback(err);
      }
      self.applyPluginsAsync("optimize-chunk-assets", self.chunks, err => {
        if(err) {
          return callback(err);
        }
        self.applyPlugins1("after-optimize-chunk-assets", self.chunks);
        self.applyPluginsAsync("optimize-assets", self.assets, err => {
          if(err) {
            return callback(err);
          }
          self.applyPlugins1("after-optimize-assets", self.assets);
          if(self.applyPluginsBailResult("need-additional-seal")) {
            self.unseal();
            return self.seal(callback);
          }
          return self.applyPluginsAsync("after-seal", callback);
        });
      });
    });
  });
}
'before run'
  'run'
    compile:func//调用compile函数
        'before compile'
           'compile'//(1)compiler对象的第一阶段
               newCompilation:object//创建compilation对象
               'make' //(2)compiler对象的第二阶段 
                    compilation.finish:func
                       "finish-modules"
                    compilation.seal
                         "seal"
                         "optimize"
                         "optimize-modules-basic"
                         "optimize-modules-advanced"
                         "optimize-modules"
                         "after-optimize-modules"//首先是优化模块
                         "optimize-chunks-basic"
                         "optimize-chunks"//然后是优化chunk
                         "optimize-chunks-advanced"
                         "after-optimize-chunks"
                         "optimize-tree"
                            "after-optimize-tree"
                            "should-record"
                            "revive-modules"
                            "optimize-module-order"
                            "advanced-optimize-module-order"
                            "before-module-ids"
                            "module-ids"//首先优化module-order,然后优化module-id
                            "optimize-module-ids"
                            "after-optimize-module-ids"
                            "revive-chunks"
                            "optimize-chunk-order"
                            "before-chunk-ids"//首先优化chunk-order,然后chunk-id
                            "optimize-chunk-ids"
                            "after-optimize-chunk-ids"
                            "record-modules"//record module然后record chunk
                            "record-chunks"
                            "before-hash"
                               compilation.createHash//func
                                 "chunk-hash"//webpack-md5-hash
                            "after-hash"
                            "record-hash"//before-hash/after-hash/record-hash
                            "before-module-assets"
                            "should-generate-chunk-assets"
                            "before-chunk-assets"
                            "additional-chunk-assets"
                            "record"
                            "additional-assets"
                                "optimize-chunk-assets"
                                   "after-optimize-chunk-assets"
                                   "optimize-assets"
                                      "after-optimize-assets"
                                      "need-additional-seal"
                                         unseal:func
                                           "unseal"
                                      "after-seal"
                    "after-compile"//(4)完成模块构建和编译过程(seal函数回调)    
    "emit"//(5)compile函数的回调,compiler开始输出assets,是改变assets最后机会
    "after-emit"//(6)文件产生完成

compiler.watch方法的调用 其本质是使用了相关的配置生成了Watching对象:

var watching = new Watching(this, watchOptions, handler);
Watching.prototype.watch = function(files, dirs, missing) {
  this.watcher = this.compiler.watchFileSystem.watch(files, dirs, missing, this.startTime, this.watchOptions, function(err, filesModified, contextModified, missingModified, fileTimestamps, contextTimestamps) {
    this.watcher = null;
    if(err) return this.handler(err);
    this.compiler.fileTimestamps = fileTimestamps;
    this.compiler.contextTimestamps = contextTimestamps;
    this.invalidate();
  }.bind(this), function(fileName, changeTime) {
    this.compiler.applyPlugins("invalid", fileName, changeTime);
  }.bind(this));
};

如果我们的文件发生了变化,那么我们直接调用Watching实例的invalidate方法,并通知compiler重新开始编译过程!这也是我们最重要的watch逻辑!这也是我们为什么有上面这样的代码:

if (args.watch) {
    compiler.watch(args.watch || 200, doneHandler);
  } else {
    compiler.run(doneHandler);
  }

compiler的watch方法返回的是一个watching,那么我们看看Watching对象的内部结构:

function Watching(compiler, watchOptions, handler) {
this.startTime = null;
this.invalid = false;//是否已经文件变化
this.error = null;
this.stats = null;
this.handler = handler;
this.compiler = compiler;//compiler句柄
this.running = true;
}
Watching.prototype._go = function() {
};
Watching.prototype._done = function(err, compilation) {
};
Watching.prototype.watch = function(files, dirs, missing) {
};

Watching.prototype.invalidate = function() {
if(this.watcher) {
 this.watcher.pause();
 this.watcher = null;
}
if(this.running) {
 this.invalid = true;
 return false;
} else {
 this._go();
}
};
Watching.prototype.close = function(callback) {
if(callback === undefined) callback = function() {};
if(this.watcher) {
 this.watcher.close();
 this.watcher = null;
}
if(this.running) {
 this.invalid = true;
 this._done = function() {
   callback();
 };
} else {
 callback();
}
};

通过上面的结果你应该可以知道invalidate和close方法的具体作用了,这里就不在赘述
compiler对象

// Run compiler.
  const compiler = webpack(webpackConfig);
  // Hack: remove extract-text-webpack-plugin log
  if (!args.verbose) {
    compiler.plugin('done', (stats) => {
      stats.stats.forEach((stat) => {
        stat.compilation.children = stat.compilation.children.filter((child) => {
          return child.name !== 'extract-text-webpack-plugin';
        });
      });
    });
  }

'done'回调是当'emit,after-emit'都调用结束了以后才会触发的,所以这时候我们所有的文件assets都已经生成结束了。
当我们调用webpack方法的时候,返回的就是compiler对象!
我们的stats对象有一个compilation属性,从构造函数就可以看到:

class Stats {
 constructor(compilation) {
   this.compilation = compilation;
   this.hash = compilation.hash;
 }
}

参考资料:
http://taobaofed.org/blog/2016/09/09/webpack-flow/

https://github.com/webpack/extract-text-webpack-plugin/issues/35

http://webpack.github.io/docs/plugins.html#the-parser-instance

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,780评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,424评论 1 305
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,397评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,576评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,997评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,945评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,107评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,850评论 0 208
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,625评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,804评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,285评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,613评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,291评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,164评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,963评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,096评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,886评论 2 278

推荐阅读更多精彩内容

  • 说在前面:这些文章均是本人花费大量精力研究整理,如有转载请联系作者并注明引用,谢谢本文的受众人群不是webpack...
    RockSAMA阅读 6,807评论 2 7
  • 函数和对象 1、函数 1.1 函数概述 函数对于任何一门语言来说都是核心的概念。通过函数可以封装任意多条语句,而且...
    道无虚阅读 4,352评论 0 5
  • 原文http://www.cnblogs.com/libin-1/p/6596810.html 版本号 vue-c...
    tengrl阅读 3,579评论 0 0
  • 1。观察——留意发生的事情,清楚表达观察结果而不判断或评估; 2。感受——表达感受如喜悦、开心、受伤、气愤等;
    ax1105文静阅读 228评论 1 0
  • 日念家人一好处,念力加持享幸福! 【先生好】洗碗做饭收拾家修暖气,总之他哪儿都好!特别是今天我生病,他一个人忙里忙...
    风潇潇blj阅读 116评论 0 0