热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:webpack源码分析3

篇首语:本文由编程笔记#小编为大家整理,主要介绍了webpack源码分析3相关的知识,希望对你有一定的参考价值。21、定位webpack打包入口01cmd

篇首语:本文由编程笔记#小编为大家整理,主要介绍了webpack源码分析3相关的知识,希望对你有一定的参考价值。


21、定位webpack打包入口

01 cmd 文件核心的作用就组装了 node */webpack/bin/webpack.js

02 webpack.js 中核心的操作就是 require 了 node_modules/webpack-cli/bin/cli.js

03 cli.js
01 当前文件一般有二个操作,处理参数,将参数交给不同的逻辑(分发业务)
02 options
03 complier
04 complier.run( 至于run 里面做了什么,后续再看,当前只关注代码入口点 )

wepack的一个流程

合并配置 compilers.beforerun

实例化compiler compilers.run

设置node文件读写的能力 compilers.beforecompile

通过循环挂载plugins compilers.compile

处理webpack内部默认的插件(入口文件) compilers.make

22、webpack手写实现


./webpack.js
const Compiler = require(\'./Compiler\')
const NodeEnvirOnmentPlugin= require(\'./node/NodeEnvironmentPlugin\')
const webpack = function (options) {
// 01 实例化 compiler 对象
let compiler = new Compiler(options.context)
compiler.optiOns= options
// 02 初始化 NodeEnvironmentPlugin(让compiler具体文件读写能力)
new NodeEnvironmentPlugin().apply(compiler)
// 03 挂载所有 plugins 插件至 compiler 对象身上
if (options.plugins && Array.isArray(options.plugins)) {
for (const plugin of options.plugins) {
plugin.apply(compiler)
}
}
// 04 挂载所有 webpack 内置的插件(入口)
// compiler.optiOns= new WebpackOptionsApply().process(options, compiler);
// 05 返回 compiler 对象即可
return compiler
}
module.exports = webpack

const {
Tapable,
AsyncSeriesHook
} = require(\'tapable\')
class Compiler extends Tapable {
constructor(context) {
super()
this.cOntext= context
this.hooks = {
done: new AsyncSeriesHook(["stats"]),//
}
}
run(callback) {
callback(null, {
toJson() {
return {
entries: [], // 当前次打包的入口信息
chunks: [], // 当前次打包的 chunk 信息
modules: [], // 模块信息
assets: [], // 当前次打包最终生成的资源
}
}
})
}
}
module.exports = Compiler

23、entryOptionPlugin

WebpackOptionsApply
process(options, compiler)
EntryOptionPlugin

entryOption 是一个钩子实例,
entryOption 在 EntryOptionPlugin 内部的 apply 方法中调用了 tap (注册了事件监听)
上述的事件监听在 new 完了 EntryOptionPlugin 之后就调用了
itemToPlugin, 它是一个函数,接收三个参数( context item ‘main)

SingleEntryPlugin
在调用 itemToPlugin, 的时候又返回了一个 实例对象
有一个构造函数,负责接收上文中的 context entry name
compilation 钩子监听
make 钩子监听


./EntryOptionPlugin.js
const SingleEntryPlugin = require("./SingleEntryPlugin")
const itemToPlugin = function (context, item, name) {
return new SingleEntryPlugin(context, item, name)
}
class EntryOptionPlugin {
apply(compiler) {
compiler.hooks.entryOption.tap(\'EntryOptionPlugin\', (context, entry) => {
itemToPlugin(context, entry, "main").apply(compiler)
})
}
}
module.exports = EntryOptionPlugin
./WebpackOptionsApply.js
const EntryOptiOnPlugin= require("./EntryOptionPlugin")
class WebpackOptionsApply {
process(options, compiler) {
new EntryOptionPlugin().apply(compiler)
compiler.hooks.entryOption.call(options.context, options.entry)
}
}
module.exports = WebpackOptionsApply

./SingleEntryPlugin.js
class SingleEntryPlugin {
constructor(context, entry, name) {
this.cOntext= context
this.entry = entry
this.name = name
}
apply(compiler) {
compiler.hooks.make.tapAsync(\'SingleEntryPlugin\', (compilation, callback) => {
const { context, entry, name } = this
console.log("make 钩子监听执行了~~~~~~")
// compilation.addEntry(context, entry, name, callback)
})
}
}
module.exports = SingleEntryPlugin

24、实现run方法


run(callback) {
console.log(\'run 方法执行了~~~~\')
const finalCallback = function (err, stats) {
callback(err, stats)
}
const OnCompiled= function (err, compilation) {
console.log(\'onCompiled~~~~\')
finalCallback(err, {
toJson() {
return {
entries: [],
chunks: [],
modules: [],
assets: []
}
}
})
}
this.hooks.beforeRun.callAsync(this, (err) => {
this.hooks.run.callAsync(this, (err) => {
this.compile(onCompiled)
})
})
}

25、实现compile方法

newCompilationParams 方法调用,返回params,normalModuleFactory

上述操作是为了获取params

接着调用beforeCompile钩子监听,在它的回调中又触发了compile监听

调用newCompilation 方法,传入上面的params,返回一个compilation

调用了createCompilation

上述操作之后出发make钩子监听

const {
Tapable,
SyncHook,
SyncBailHook,
AsyncSeriesHook,
AsyncParallelHook
} = require(\'tapable\')
const NormalModuleFactory = require(\'./NormalModuleFactory\')
const Compilation = require(\'./Compilation\')
class Compiler extends Tapable {
constructor(context) {
super()
this.cOntext= context
this.hooks = {
done: new AsyncSeriesHook(["stats"]),
entryOption: new SyncBailHook(["context", "entry"]),
beforeRun: new AsyncSeriesHook(["compiler"]),
run: new AsyncSeriesHook(["compiler"]),
thisCompilation: new SyncHook(["compilation", "params"]),
compilation: new SyncHook(["compilation", "params"]),
beforeCompile: new AsyncSeriesHook(["params"]),
compile: new SyncHook(["params"]),
make: new AsyncParallelHook(["compilation"]),
afterCompile: new AsyncSeriesHook(["compilation"]),
}
}
run(callback) {
console.log(\'run 方法执行了~~~~\')
const finalCallback = function (err, stats) {
callback(err, stats)
}
const OnCompiled= function (err, compilation) {
console.log(\'onCompiled~~~~\')
finalCallback(err, {
toJson() {
return {
entries: [],
chunks: [],
modules: [],
assets: []
}
}
})
}
this.hooks.beforeRun.callAsync(this, (err) => {
this.hooks.run.callAsync(this, (err) => {
this.compile(onCompiled)
})
})
}
compile(callback) {
const params = this.newCompilationParams()
this.hooks.beforeRun.callAsync(params, (err) => {
this.hooks.compile.call(params)
const compilation = this.newCompilation(params)
this.hooks.make.callAsync(compilation, (err) => {
console.log(\'make钩子监听触发了~~~~~\')
callback()
})
})
}
newCompilationParams() {
const params = {
normalModuleFactory: new NormalModuleFactory()
}
return params
}
newCompilation(params) {
const compilation = this.createCompilation()
}
createCompilation() {
return new Compilation(this)
}
}
module.exports = Compiler

26、make流程实现

一、步骤
01 实例化 compiler 对象( 它会贯穿整个webpack工作的过程 )
02 由 compiler 调用 run 方法

二、compiler 实例化操作
01 compiler 继承 tapable,因此它具备钩子的操作能力(监听事件,触发事件,webpack是一个事件流)

02 在实例化了 compiler 对象之后就往它的身上挂载很多属性,其中 NodeEnvironmentPlugin 这个操作就让它具备了
文件读写的能力(我们的模拟时采用的是 node 自带的 fs )

03 具备了 fs 操作能力之后又将 plugins 中的插件都挂载到了 compiler 对象身上

04 将内部默认的插件与 compiler 建立关系,其中 EntryOptionPlugin 处理了入口模块的 id

05 在实例化 compiler 的时候只是监听了 make 钩子(SingleEntryPlugin)

5-1 在 SingleEntryPlugin 模块的 apply 方法中有二个钩子监听
5-2 其中 compilation 钩子就是让 compilation 具备了利用 normalModuleFactory 工厂创建一个普通模块的能力
5-3 因为它就是利用一个自己创建的模块来加载需要被打包的模块
5-4 其中 make 钩子 在 compiler.run 的时候会被触发,走到这里就意味着某个模块执行打包之前的所有准备工作就完成了
5-5 addEntry 方法调用()

三、run 方法执行( 当前想看的是什么时候触发了 make 钩子 )

01 run 方法里就是一堆钩子按着顺序触发(beforeRun run compile)

02 compile 方法执行

1 准备参数(其中 normalModuleFactory 是我们后续用于创建模块的)
2 触发beforeCompile
3 将第一步的参数传给一个函数,开始创建一个 compilation (newCompilation)
4 在调用 newCompilation 的内部
- 调用了 createCompilation
- 触发了 this.compilation 钩子 和 compilation 钩子的监听

03 当创建了 compilation 对象之后就触发了 make 钩子

04 当我们触发 make 钩子监听的时候,将 compilation 对象传递了过去

四、总结

1 实例化 compiler
2 调用 compile 方法
3 newCompilation
4 实例化了一个compilation 对象(它和 compiler 是有关系)
5 触发 make 监听
6 addEntry 方法(这个时候就带着 context name entry 一堆的东西) 就奔着编译去了.....

WebpackOptionsApply
process(options, compiler)
EntryOptionPlugin
entryOption 是一个钩子实例,
entryOption 在 EntryOptionPlugin 内部的 apply 方法中调用了 tap (注册了事件监听)
上述的事件监听在 new 完了 EntryOptionPlugin 之后就调用了
itemToPlugin, 它是一个函数,接收三个参数( context item \'main)
SingleEntryPlugin
在调用 itemToPlugin, 的时候又返回了一个 实例对象
有一个构造函数,负责接收上文中的 context entry name
compilation 钩子监听
make 钩子监听

27、addEntry流程分析

01 make 钩子在被触发的时候,接收到了 compilation 对象实现,它的身上挂载了很多内容

02 从 compilation 当中解构了三个值
entry : 当前需要被打包的模块的相对路径(./src/index.js)
name: main
context: 当前项目的根路径

03 dep 是对当前的入口模块中的依赖关系进行处理

04 调用了 addEntry 方法

05 在 compilation实例的身上有一个 addEntry 方法,然后内部调用了 _addModuleChain 方法,去处理依赖

06 在 compilation 当中我们可以通过 NormalModuleFactory 工厂来创建一个普通的模块对象

07 在 webpack 内部默认启了一个 100 并发量的打包操作,当前我们看到的是 normalModule.create()

08 在 beforeResolve 里面会触发一个 factory 钩子监听【 这个部分的操作其实是处理 loader, 当前我们重点去看 】

09 上述操作完成之后获取到了一个函数被存在 factory 里,然后对它进行了调用

10 在这个函数调用里又触发了一个叫 resolver 的钩子( 处理 loader的,拿到了 resolver方法就意味着所有的Loader 处理完毕 )

11 调用 resolver() 方法之后,就会进入到 afterResolve 这个钩子里,然后就会触发 new NormalModule

12 在完成上述操作之后就将module 进行了保存和一些其它属性的添加

13 调用 buildModule 方法开始编译---》 调用 build ---》doBuild

28、addEntry方法实现


const path = require(\'path\')
const Parser = require(\'./Parser\')
const NormalModuleFactory = require(\'./NormalModuleFactory\')
const { Tapable, SyncHook } = require(\'tapable\')
// 实例化一个 normalModuleFactory parser
const normalModuleFactory = new NormalModuleFactory()
const parser = new Parser()
class Compilation extends Tapable {
constructor(compiler) {
super()
this.compiler = compiler
this.cOntext= compiler.context
this.optiOns= compiler.options
// 让 compilation 具备文件的读写能力
this.inputFileSystem = compiler.inputFileSystem
this.outputFileSystem = compiler.outputFileSystem
this.entries = [] // 存入所有入口模块的数组
this.modules = [] // 存放所有模块的数据
this.hooks = {
succeedModule: new SyncHook([\'module\'])
}
}
/**
* 完成模块编译操作
* @param {*} context 当前项目的根
* @param {*} entry 当前的入口的相对路径
* @param {*} name chunkName main
* @param {*} callback 回调
*/
addEntry(context, entry, name, callback) {
this._addModuleChain(context, entry, name, (err, module) => {
callback(err, module)
})
}
_addModuleChain(context, entry, name, callback) {
let entryModule = normalModuleFactory.create({
name,
context,
rawRequest: entry,
resource: path.posix.join(context, entry), // 当前操作的核心作用就是返回 entry 入口的绝对路径
parser
})
const afterBuild = function (err) {
callback(err, entryModule)
}
this.buildModule(entryModule, afterBuild)
// 当我们完成了本次的 build 操作之后将 module 进行保存
this.entries.push(entryModule)
this.modules.push(entryModule)
}
/**
* 完成具体的 build 行为
* @param {*} module 当前需要被编译的模块
* @param {*} callback
*/
buildModule(module, callback) {
module.build(this, (err) => {
// 如果代码走到这里就意味着当前 Module 的编译完成了
this.hooks.succeedModule.call(module)
callback(err)
})
}
}
module.exports = Compilation

NormalModuleFactory
const NormalModule = require("./NormalModule")
class NormalModuleFactory {
create(data) {
return new NormalModule(data)
}
}
module.exports = NormalModuleFactory

./NormalModule
class NormalModule {
constructor(data) {
this.name = data.name
this.entry = data.entry
this.rawRequest = data.rawRequest
this.parser = data.parser // TODO: 等待完成
this.resource = data.resource
this._source // 存放某个模块的源代码
this._ast // 存放某个模板源代码对应的 ast
}
build(compilation, callback) {
/**
* 01 从文件中读取到将来需要被加载的 module 内容,这个
* 02 如果当前不是 js 模块则需要 Loader 进行处理,最终返回 js 模块
* 03 上述的操作完成之后就可以将 js 代码转为 ast 语法树
* 04 当前 js 模块内部可能又引用了很多其它的模块,因此我们需要递归完成
* 05 前面的完成之后,我们只需要重复执行即可
*/
this.doBuild(compilation, (err) => {
this._ast = this.parser.parse(this._source)
callback(err)
})
}
doBuild(compilation, callback) {
this.getSource(compilation, (err, source) => {
this._source = source
callback()
})
}
getSource(compilation, callback) {
compilation.inputFileSystem.readFile(this.resource, \'utf8\', callback)
}
}
module.exports = NormalModule

./parser
const babylon = require(\'babylon\')
const { Tapable } = require(\'tapable\')
class Parser extends Tapable {
parse(source) {
return babylon.parse(source, {
sourceType: \'module\',
plugins: [\'dynamicImport\'] // 当前插件可以支持 import() 动态导入的语法
})
}
}
module.exports = Parser
const {
Tapable,
SyncHook,
SyncBailHook,
AsyncSeriesHook,
AsyncParallelHook
} = require(\'tapable\')
const Stats = require(\'./Stats\')
const NormalModuleFactory = require(\'./NormalModuleFactory\')
const Compilation = require(\'./Compilation\')
class Compiler extends Tapable {
constructor(context) {
super()
this.cOntext= context
this.hooks = {
done: new AsyncSeriesHook(["stats"]),
entryOption: new SyncBailHook(["context", "entry"]),
beforeRun: new AsyncSeriesHook(["compiler"]),
run: new AsyncSeriesHook(["compiler"]),
thisCompilation: new SyncHook(["compilation", "params"]),
compilation: new SyncHook(["compilation", "params"]),
beforeCompile: new AsyncSeriesHook(["params"]),
compile: new SyncHook(["params"]),
make: new AsyncParallelHook(["compilation"]),
afterCompile: new AsyncSeriesHook(["compilation"]),
}
}
run(callback) {
console.log(\'run 方法执行了~~~~\')
const finalCallback = function (err, stats) {
callback(err, stats)
}
const OnCompiled= function (err, compilation) {
console.log(\'onCompiled~~~~\')
finalCallback(err, new Stats(compilation))
}
this.hooks.beforeRun.callAsync(this, (err) => {
this.hooks.run.callAsync(this, (err) => {
this.compile(onCompiled)
})
})
}
compile(callback) {
const params = this.newCompilationParams()
this.hooks.beforeRun.callAsync(params, (err) => {
this.hooks.compile.call(params)
const compilation = this.newCompilation(params)
this.hooks.make.callAsync(compilation, (err) => {
console.log(\'make钩子监听触发了~~~~~\')
callback(err, compilation)
})
})
}
newCompilationParams() {
const params = {
normalModuleFactory: new NormalModuleFactory()
}
return params
}
newCompilation(params) {
const compilation = this.createCompilation()
this.hooks.thisCompilation.call(compilation, params)
this.hooks.compilation.call(compilation, params)
return compilation
}
createCompilation() {
return new Compilation(this)
}
}
module.exports = Compiler

29、模块依赖

01 需要将 Index.js 里的 require 方法替换成 webpack_require
02 还有将 ./title 替换成 ./src/title.js

03 实现递归的操作 ,所以要将依赖的模块信息保存好,方便交给下一次 create


./NormalModule.js

build(compilation, callback) {
/**
* 01 从文件中读取到将来需要被加载的 module 内容,这个
* 02 如果当前不是 js 模块则需要 Loader 进行处理,最终返回 js 模块
* 03 上述的操作完成之后就可以将 js 代码转为 ast 语法树
* 04 当前 js 模块内部可能又引用了很多其它的模块,因此我们需要递归完成
* 05 前面的完成之后,我们只需要重复执行即可
*/
this.doBuild(compilation, (err) => {
this._ast = this.parser.parse(this._source)
// 这里的 _ast 就是当前 module 的语法树,我们可以对它进行修改,最后再将 ast 转回成 code 代码
traverse(this._ast, {
CallExpression: (nodePath) => {
let node = nodePath.node
// 定位 require 所在的节点
if (node.callee.name === \'require\') {
// 获取原始请求路径
let modulePath = node.arguments[0].value // \'./title\'
// 取出当前被加载的模块名称
let moduleName = modulePath.split(path.posix.sep).pop() // title
// [当前我们的打包器只处理 js ]
let extName = moduleName.indexOf(\'.\') == -1 ? \'.js\' : \'\'
moduleName += extName // title.js
// 【最终我们想要读取当前js里的内容】 所以我们需要个绝对路径
let depResource = path.posix.join(path.posix.dirname(this.resource), moduleName)
// 【将当前模块的 id 定义OK】
let depModuleId = \'./\' + path.posix.relative(this.context, depResource) // ./src/title.js
// 记录当前被依赖模块的信息,方便后面递归加载
this.dependencies.push({
name: this.name, // TODO: 将来需要修改
context: this.context,
rawRequest: moduleName,
moduleId: depModuleId,
resource: depResource
})
// 替换内容
node.callee.name = \'__webpack_require__\'
node.arguments = [types.stringLiteral(depModuleId)]
}
}
})
// 上述的操作是利用ast 按要求做了代码修改,下面的内容就是利用 .... 将修改后的 ast 转回成 code
let { code } = generator(this._ast)
this._source = code
callback(err)
})
./compilation
const path = require(\'path\')
const async = require(\'neo-async\')
const Parser = require(\'./Parser\')
const NormalModuleFactory = require(\'./NormalModuleFactory\')
const { Tapable, SyncHook } = require(\'tapable\')
// 实例化一个 normalModuleFactory parser
const normalModuleFactory = new NormalModuleFactory()
const parser = new Parser()
class Compilation extends Tapable {
constructor(compiler) {
super()
this.compiler = compiler
this.cOntext= compiler.context
this.optiOns= compiler.options
// 让 compilation 具备文件的读写能力
this.inputFileSystem = compiler.inputFileSystem
this.outputFileSystem = compiler.outputFileSystem
this.entries = [] // 存入所有入口模块的数组
this.modules = [] // 存放所有模块的数据
this.hooks = {
succeedModule: new SyncHook([\'module\'])
}
}
/**
* 完成模块编译操作
* @param {*} context 当前项目的根
* @param {*} entry 当前的入口的相对路径
* @param {*} name chunkName main
* @param {*} callback 回调
*/
addEntry(context, entry, name, callback) {
this._addModuleChain(context, entry, name, (err, module) => {
callback(err, module)
})
}
_addModuleChain(context, entry, name, callback) {
this.createModule({
parser,
name: name,
context: context,
rawRequest: entry,
resource: path.posix.join(context, entry),
moduleId: \'./\' + path.posix.relative(context, path.posix.join(context, entry))
}, (entryModule) => {
this.entries.push(entryModule)
}, callback)
}
/**
* 定义一个创建模块的方法,达到复用的目的
* @param {*} data 创建模块时所需要的一些属性值
* @param {*} doAddEntry 可选参数,在加载入口模块的时候,将入口模块的id 写入 this.entries
* @param {*} callback
*/
createModule(data, doAddEntry, callback) {
let module = normalModuleFactory.create(data)
const afterBuild = (err, module) => {
// 使用箭头函数可以保证this指向在定义时就确定
// 在 afterBuild 当中我们就需要判断一下,当前次module 加载完成之后是否需要处理依赖加载
if (module.dependencies.length > 0) {
// 当前逻辑就表示module 有需要依赖加载的模块,因此我们可以再单独定义一个方法来实现
this.processDependencies(module, (err) => {
callback(err, module)
})
} else {
callback(err, module)
}
}
this.buildModule(module, afterBuild)
// 当我们完成了本次的 build 操作之后将 module 进行保存
doAddEntry && doAddEntry(module)
this.modules.push(module)
}
/**
* 完成具体的 build 行为
* @param {*} module 当前需要被编译的模块
* @param {*} callback
*/
buildModule(module, callback) {
module.build(this, (err) => {
// 如果代码走到这里就意味着当前 Module 的编译完成了
this.hooks.succeedModule.call(module)
callback(err, module)
})
}
processDependencies(module, callback) {
// 01 当前的函数核心功能就是实现一个被依赖模块的递归加载
// 02 加载模块的思想都是创建一个模块,然后想办法将被加载模块的内容拿进来?
// 03 当前我们不知道 module 需要依赖几个模块, 此时我们需要想办法让所有的被依赖的模块都加载完成之后再执行 callback?【 neo-async 】
let dependencies = module.dependencies
async.forEach(dependencies, (dependency, done) => {
this.createModule({
parser,
name: dependency.name,
context: dependency.context,
rawRequest: dependency.rawRequest,
moduleId: dependency.moduleId,
resource: dependency.resource
}, null, done)
}, callback)
}
}
module.exports = Compilation

30、chunk流程分析


推荐阅读
  • 1.安装cordovapluginaddcordova-plugin-file-transfercordovapluginaddcordova-plugin-fi ... [详细]
  • Webpack5内置处理图片资源的配置方法
    本文介绍了在Webpack5中处理图片资源的配置方法。在Webpack4中,我们需要使用file-loader和url-loader来处理图片资源,但是在Webpack5中,这两个Loader的功能已经被内置到Webpack中,我们只需要简单配置即可实现图片资源的处理。本文还介绍了一些常用的配置方法,如匹配不同类型的图片文件、设置输出路径等。通过本文的学习,读者可以快速掌握Webpack5处理图片资源的方法。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 本文介绍了PE文件结构中的导出表的解析方法,包括获取区段头表、遍历查找所在的区段等步骤。通过该方法可以准确地解析PE文件中的导出表信息。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • npminstall-Dbabelcorebabelpreset-envbabelplugin-transform-runtimebabelpolyfillbabel-loader ... [详细]
  • PHP并发读写文件问题 高手请进! ... [详细]
  • HSE8MHz。配置前将所有RCC重置为初始值RCC_DeInit();*这里选择外部晶振(HSE)作为时钟源,因此首先打开外部晶振*RC ... [详细]
  • Vue生产环境调试的方法步骤
    开发环境下Vue会提供很多警告来帮你对付常见的错误与陷阱,而在生产环境下,这些警告语句却没有用,反而会增加应用的体积,下面这篇文章主要给大家介绍了关于Vue生产环境调试的方法步骤, ... [详细]
  • vue组件component的注册与使用详解_vue.js
    组件是Vue是一个可以重复使用的Vue实例, 它拥有独一无二的组件名称,它可以扩展HTML元素,以组件名称的方式作为自定义的HTML标签,这篇文章主要介绍了vue组件compone ... [详细]
  • VUE2 实现一个页面 调用 另一个页面的函数
    今天我在Vue 需要实现 一个这样的功能在App.vue页面中 要实现 调用 另一个页(ExtensionMonitor.vue)中的函数 并将参数 传过去下面将实现的步骤写下来, ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • Linux如何安装Mongodb的详细步骤和注意事项
    本文介绍了Linux如何安装Mongodb的详细步骤和注意事项,同时介绍了Mongodb的特点和优势。Mongodb是一个开源的数据库,适用于各种规模的企业和各类应用程序。它具有灵活的数据模式和高性能的数据读写操作,能够提高企业的敏捷性和可扩展性。文章还提供了Mongodb的下载安装包地址。 ... [详细]
  • React项目中运用React技巧解决实际问题的总结
    本文总结了在React项目中如何运用React技巧解决一些实际问题,包括取消请求和页面卸载的关联,利用useEffect和AbortController等技术实现请求的取消。文章中的代码是简化后的例子,但思想是相通的。 ... [详细]
  • ps:写的第一个,不足之处,欢迎拍砖---只是想用自己的方法一步步去实现一些框架看似高大上的小功能(比如说模型中的toArraytoJsonsetAtt ... [详细]
author-avatar
平從老
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有