您当前的位置:首页 > 电脑百科 > 程序开发 > 前端

前端面试题-工程化-webpack 编译流程

时间:2023-03-23 12:01:06  来源:今日头条  作者:前端餐厅

webpack 编译流程

  1. 初始化参数:从配置文件和 Shell 语句中读取并合并参数,得出最终的配置对象
  2. 用上一步得到的参数初始化 Compiler 对象
  3. 加载所有配置的插件
  4. 执行对象的 run 方法开始执行编译
  5. 根据配置中的entry找出入口文件
  6. 从入口文件出发,调用所有配置的Loader对模块进行编译
  7. 再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
  8. 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk
  9. 再把每个 Chunk 转换成一个单独的文件加入到输出列表
  10. 在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统

在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果

 

1.1entry​

srcentry1.js

let title = require("./title")
console.log("entry12", title)

srcentry2.js

let title = require("./title.js")
console.log("entry2", title)

srctitle.js

module.exports = "title"

1.2loader.js​

  • loader 的本质就是一个函数,一个用于转换或者说翻译的函数
  • 把那些 webpack 不认识的模块 less sass baxx 转换为 webpack 能认识的模块 js json

loaderslogger1-loader.js

function loader1(source) {
  //let name= 'entry1';
  return source + "//logger1" //let name= 'entry1';//logger1
}
module.exports = loader1

loaderslogger2-loader.js

function loader2(source) {
  //let name= 'entry1';
  return source + "//logger2" //let name= 'entry1';//logger2
}
module.exports = loader2

1.3 plugin.js​

pluginsdone-plugin.js

class DonePlugin {
  Apply(compiler) {
    compiler.hooks.done.tap("DonePlugin", () => {
      console.log("done:结束编译")
    })
  }
}
module.exports = DonePlugin

pluginsrun1-plugin.js

class RunPlugin {
  apply(compiler) {
    //在此插件里可以监听run这个钩子
    compiler.hooks.run.tap("Run1Plugin", () => {
      console.log("run1:开始编译")
    })
  }
}
module.exports = RunPlugin

pluginsrun2-plugin.js

class RunPlugin {
  apply(compiler) {
    compiler.hooks.run.tap("Run2Plugin", () => {
      console.log("run2:开始编译")
    })
  }
}
module.exports = RunPlugin

1.4 webpack.config.js​

webpack.config.js

const path = require("path")
const Run1Plugin = require("./plugins/run1-plugin")
const Run2Plugin = require("./plugins/run2-plugin")
const DonePlugin = require("./plugins/done-plugin")
module.exports = {
  mode: "development",
  devtool: false,
  context: process.cwd,
  entry: {
    entry1: "./src/entry1.js",
    entry2: "./src/entry2.js",
  },
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "[name].js",
  },
  resolve: {
    extensions: [".js", ".jsx", ".tx", ".tsx"],
  },
  module: {
    rules: [
      {
        test: /.js$/,
        use: [path.resolve(__dirname, "loaders/loader2.js"), path.resolve(__dirname, "loaders/loader1.js")],
      },
    ],
  },
  plugins: [new DonePlugin(), new Run2Plugin(), new Run1Plugin()],
}

1.5debugger.js​

debugger.js

const fs = require("fs")
const webpack = require("./webpack2")
// const webpack = require("webpack")
const webpackConfig = require("./webpack.config")
debugger
const compiler = webpack(webpackConfig)
//4.执行`Compiler`对象的 run 方法开始执行编译
compiler.run((err, stats) => {
  if (err) {
    console.log(err)
  } else {
    //stats代表统计结果对象
    const statsJson = JSON.stringify(
      stats.toJson({
        // files: true, //代表打包后生成的文件
        assets: true, //其实是一个代码块到文件的对应关系
        chunks: true, //从入口模块出发,找到此入口模块依赖的模块,或者依赖的模块依赖的模块,合在一起组成一个代码块
        modules: true, //打包的模块 每个文件都是一个模块
      })
    )

    fs.writeFileSync("./statsJson.json", statsJson)
  }
})

1.6 webpack.js​

webpack2.js

const Compiler = require("./Compiler")
function webpack(options) {
  // 1.初始化参数:从配置文件和 Shell 语句中读取并合并参数,得出最终的配置对象
  //argv[0]是Node程序的绝对路径 argv[1] 正在运行的脚本
  // node debugger --mode=production
  const argv = process.argv.slice(2)
  const shellOptions = argv.reduce((shellOptions, options) => {
    // options = '--mode=development'
    const [key, value] = options.split("=")
    shellOptions[key.slice(2)] = value
    return shellOptions
  }, {})
  console.log("shellOptions=>", shellOptions)
  const finalOptions = { ...options, ...shellOptions }
  //2.用上一步得到的参数初始化 `Compiler` 对象
  const compiler = new Compiler(finalOptions)
  //3.加载所有配置的插件
  const { plugins } = finalOptions
  for (let plugin of plugins) {
    //订阅钩子
    plugin.apply(compiler)
  }
  return compiler
}
module.exports = webpack

1.7 Compilation​

Compiler.js

const { SyncHook } = require("tapable")
const Compilation = require("./Compilation")
const fs = require("fs")
const path = require("path")
// Compiler 模块是 webpack 的主要引擎
class Compiler {
  constructor(options) {
    this.options = options
    this.hooks = {
      run: new SyncHook(), //在开始编译之前调用
      done: new SyncHook(), //在编译完成时执行
    }
  }
  run(callback) {
    this.hooks.run.call() //在编译开始前触发run钩子执行
    //在编译的过程中会收集所有的依赖的模块或者说文件
    //stats指的是统计信息 modules chunks  files=bundle assets指的是文件名和文件内容的映射关系
    const onCompiled = (err, stats, fileDependencies) => {
      // 10.在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
      for (let filename in stats.assets) {
        let filePath = path.join(this.options.output.path, filename)
        fs.writeFileSync(filePath, stats.assets[filename], "utf8")
      }
      callback(err, { toJson: () => stats })
      for (let fileDependency of fileDependencies) {
        //监听依赖的文件变化,如果依赖的文件变化后会开始一次新的编译
        fs.watch(fileDependency, () => this.compile(onCompiled))
      }
    }
    this.hooks.done.call() //在编译完成时触发done钩子执行
    //调用compile方法进行编译  开始一次新的编译
    this.compile(onCompiled)
  }
  //开启一次新的编译
  compile(callback) {
    //每次编译 都会创建一个新的Compilation实例
    let compilation = new Compilation(this.options, this)
    compilation.build(callback)
  }
}
module.exports = Compiler

1.8 Compilation​

Compilation.js

const path = require("path")
const fs = require("fs")
const parser = require("@babel/parser")
const types = require("@babel/types")
const traverse = require("@babel/traverse").default
const generator = require("@babel/generator").default
const baseDir = normalizePath(process.cwd())
function normalizePath(path) {
  return path.replace(/\/g, "/")
}
class Compilation {
  constructor(options, compiler) {
    this.options = options // 配置参数
    this.options.context = this.options.context || normalizePath(process.cwd())
    this.compiler = compiler
    this.modules = [] //这里放置本次编译涉及的所有的模块
    this.chunks = [] //本次编译所组装出的代码块
    this.assets = {} // 存放输出的文件 key是文件名,值是文件内容
    this.files = [] //代表本次打包出来的文件
    this.fileDependencies = new Set() //本次编译依赖的文件或者说模块
  }
  build(callback) {
    //5.根据配置中的entry找出入口文件
    let entry = {}
    //格式化入口文件
    if (typeof this.options.entry === "string") {
      entry.mAIn = this.options.entry
    } else {
      entry = this.options.entry
    }
    // 对入口进行遍历
    for (let entryName in entry) {
      //获取入口文件的绝对路径
      let entryFilePath = path.posix.join(baseDir, entry[entryName])
      //把此入口文件添加到文件依赖列表中
      this.fileDependencies.add(entryFilePath)
      //6.从入口文件出发,调用所有配置的Loader对模块进行编译
      let entryModule = this.buildModule(entryName, entryFilePath)
      // this.modules.push(entryModule)
      // 8.根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk
      let chunk = {
        name: entryName, //入口名称
        entryModule, //入口的模块 ./src/entry.js
        modules: this.modules.filter((module) => module.names.includes(entryName)), //此入口对应的模块
      }
      this.chunks.push(chunk)
    }

    // 9.再把每个 Chunk 转换成一个单独的文件加入到输出列表
    this.chunks.forEach((chunk) => {
      const filename = this.options.output.filename.replace("[name]", chunk.name)
      this.files.push(filename)
      //组装chunk
      this.assets[filename] = getSource(chunk)
    })
    callback(
      null,
      {
        modules: this.modules,
        chunks: this.chunks,
        assets: this.assets,
        files: this.files,
      },
      this.fileDependencies
    )
  }
  /**
   * 编译模块
   * @param {*} name 模块所属的代码块(chunk)的名称,也就是entry的name entry1 entry2
   * @param {*} modulePath 模块的绝对路径
   */
  buildModule(entryName, modulePath) {
    //1.读取文件的内容
    let rawSourceCode = fs.readFileSync(modulePath, "utf8")
    //获取loader的配置规则
    let { rules } = this.options.module
    //根据规则找到所有的匹配的loader 适用于此模块的所有loader
    let loaders = []
    rules.forEach((rule) => {
      //用模块路径匹配正则表达式
      if (modulePath.match(rule.test)) {
        loaders.push(...rule.use)
      }
    })
    //调用所有配置的Loader对模块进行转换

    let transformedSourceCode = loaders.reduceRight((sourceCode, loaderPath) => {
      const loaderFn = require(loaderPath)
      return loaderFn(sourceCode)
    }, rawSourceCode)

    //7.再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
    //获取当前模块,也就是 ./src/entry1.js的模块ID
    let moduleId = "./" + path.posix.relative(baseDir, modulePath)
    //创建一个模块ID就是相对于根目录的相对路径 dependencies就是此模块依赖的模块
    //name是模块所属的代码块的名称,如果一个模块属于多个代码块,那么name就是一个数组
    let module = { id: moduleId, dependencies: new Set(), names: [entryName] }
    this.modules.push(module)
    let ast = parser.parse(transformedSourceCode, { sourceType: "module" })
    //Visitor是babel插件中的概念,此处没有
    traverse(ast, {
      CallExpression: ({ node }) => {
        //如果调用的方法名是require的话,说明就要依赖一个其它模块
        if (node.callee.name === "require") {
          // .代表当前的模块所有的目录,不是工作目录
          let depModuleName = node.arguments[0].value //"./title"
          let depModulePath
          //获取当前的模块所在的目录
          if (depModuleName.startsWith(".")) {
            //暂时先不考虑node_modules里的模块,先只考虑相对路径
            const currentDir = path.posix.dirname(modulePath)
            //要找当前模块所有在的目录下面的相对路径
            depModulePath = path.posix.join(currentDir, depModuleName)
            //此绝对路径可能没有后续,需要尝试添加后缀
            // 获取配置的扩展名后缀
            const extensions = this.options.resolve.extensions
            //尝试添加后缀 返回最终的路径
            depModulePath = tryExtensions(depModulePath, extensions)
          } else {
            //如果不是以.开头的话,就是第三方模块
            depModulePath = require.resolve(depModuleName)
          }
          //把依赖的模块路径添加到文件依赖列表
          this.fileDependencies.add(depModulePath)
          //获取此依赖的模块的ID, 也就是相对于根目录的相对路径
          let depModuleId = "./" + path.posix.relative(baseDir, depModulePath)
          //修改语法树,把依赖的模块名换成模块ID
          node.arguments[0] = types.stringLiteral(depModuleId)
          //把依赖的模块ID和依赖的模块路径放置到当前模块的依赖数组中
          module.dependencies.add({ depModuleId, depModulePath })
        }
      },
    })
    //转换源代码,把转换后的源码放在_source属性,用于后面写入文件
    let { code } = generator(ast)
    module._source = code
    ;[...module.dependencies].forEach(({ depModuleId, depModulePath }) => {
      //判断此依赖的模块是否已经打包过了或者说编译 过了
      let existModule = this.modules.find((module) => module.id === depModuleId)
      if (existModule) {
        existModule.names.push(entryName)
      } else {
        let depModule = this.buildModule(entryName, depModulePath)
        this.modules.push(depModule)
      }
    })
    return module
  }
}
/**
 *
 * @param {*} modulePath
 * @param {*} extensions
 * @returns
 */
function tryExtensions(modulePath, extensions) {
  if (fs.existsSync(modulePath)) {
    return modulePath
  }
  for (let i = 0; i < extensions.length; i++) {
    let filePath = modulePath + extensions[i]
    if (fs.existsSync(filePath)) {
      return filePath
    }
  }
  throw new Error(`找不到${modulePath}`)
}
function getSource(chunk) {
  return `
  (() => {
    var modules = {
      ${chunk.modules
        .filter((module) => module.id !== chunk.entryModule.id)
        .map(
          (module) => `
          "${module.id}": module => {
            ${module._source}
          }
        `
        )
        .join(",")}
    };
    var cache = {};
    function require(moduleId) {
      var cachedModule = cache[moduleId];
      if (cachedModule !== undefined) {
        return cachedModule.exports;
      }
      var module = cache[moduleId] = {
        exports: {}
      };
      modules[moduleId](module, module.exports, require);
      return module.exports;
    }
    var exports = {};
    (() => {
      ${chunk.entryModule._source}
    })();
  })();
  `
}
module.exports = Compilation

webpack2.js

总结​

1. 文件作用​

webpack.js 文件​

webpack 方法

  1. 接收 webpack.config.js 参数,返回 compiler 实例
  2. 初始化参数
  3. 始化 Compiler 对象实例
  4. 加载所有配置的插件

Compiler文件​

  • Compiler 模块是 webpack 的主要引擎
  • constructor 方法: 初始化一些 hooks
  • run 方法
    • 执行插件订阅的一系列 hooks
    • 创建 Compilation 实例并执行实例的 build(onCompiled)方法(开启一次新的编译)
    • onCompiled 回调在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统执行 compiler.run 方法的回调,传入 info监听依赖的文件变化,如果依赖的文件变化后会开始一次新的编译

Compilation 文件​

build 方法

  1. .根据配置中的 entry 找出入口文件
  2. 从入口文件出发,调用所有配置的 Loader 对模块进行编译
  3. 再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
  4. 根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk
  5. 再把每个 Chunk 转换成一个单独的文件加入到输出列表
  6. 执行成功后的回调

2. 流程总结​

初始化参数:​

  1. 初始化参数:从配置文件和 Shell 语句中读取并合并参数,得出最终的配置对象(命令行优先级高)

开始编译​

  1. 用上一步得到的参数初始化 Compiler 对象
    1. 初始化 options 参数和 hooks ( run: new SyncHook(), //在开始编译之前调用...)
  2. 加载所有配置的插件
    1. 在配置中找到 plugins 数组
    2. 遍历 plugins 执行每个插件的 apply 方法,并把 compiler 实例传进去(每个插件都有一个 apply 方法)
    3. 执行 compiler.hooks.run.tap等方法注册事件
  3. 执行compiler实例的 run 方法开始执行编译
    1. 整个过程伴随着触发插件的注册个各种钩子函数 this.hooks.done.call()...
    2. 开启一次新的编译,创建一个新的 Compilation 实例
    3. 执行实例的 build 方法,传入完成的回调

编译模块​

  1. 根据配置中的 entry 找出入口文件
    1. 格式化入口文件,变成对象形式
    2. 对入口进行遍历,获取入口文件的绝对路径,添加到文件依赖列表中
  2. loader 转换:从入口文件出发,调用所有配置的 Loader 对模块进行转换 (最终返回 module 对象)
    1. 读取处理文件的内容
    2. 根据规则找到所有的匹配的 loader
    3. 调用所有配置的 Loader 对模块进行转换(从上到下,从右向左)
    4. 获取当前模块模块 id,相对于根目录的相对路径
    5. 创建一个 module 对象
    6. const module = {
      id:'./src/entry1.js',//相对于根目录的相对路径
      dependencies:[{depModuleId:./src/title.js,depModulePath:'xxx'}],//dependencies就是此模块依赖的模块
      names:['entry1'],// name是模块所属的代码块的名称,如果一个模块属于多个代码块,那么name就是一个数组
      2.
      _source:'xxx',//存放对应的源码
      }
  3. 编译模块分析依赖,再递归遍历本步骤直到所有入口依赖模块的文件都经过了本步骤的处理
    1. 将 loader 编译后的代码调用 parse 转换为 ast
    2. 遍历语法树,如果存在 require 或者 import,说明就要依赖一个其它模块
    3. 获取依赖模块的绝对路径,添加到文件依赖列表中
    4. 获取此依赖的模块的 ID, 也就是相对于根目录的相对路径
    5. 修改语法树,把依赖的模块名换成模块 ID
    6. 把依赖的模块 ID 和依赖的模块路径放置到当前模块 module 的依赖数组中
    7. 调用 generator(ast),把转换后的源码放在 module._source 属性,用于后面写入文件
    8. 遍历module.dependencies,递归构建 module,构建好的存储到 this.modules 上,如果第二个入口也依赖该模块,直接取用,只需要给该模块的 name 属性上添加上入口信息

输出资源​

  1. 组装 chuck 对象:
    1. 组装
  2. const chuck = {
    name: "entry1", //入口名称
    entryModule, //入口的模块的module {id,name,dependencies,_source}
    modules: [{}], // 入口依赖模块的集合
    }
    1. this.chunks.push(chunk)

生成 bundle 文件​

  1. 把每个 Chunk 转换成一个单独的文件加入到输出列表获取要生成的文件名称并把文件名添加到 this.files 中获取文件内容并给 this.assets 对象执行 compilation.build 方法的回调

写入文件​

  1. 在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统


Tags:webpack   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
如果能重来,你要选 Vite 还是 Webpack ?
Webpack 的第一次发布是在 2013 年发布,长久以来是主流的前端打包工具。Vite 的第一次发布是在 2021 年,是近两年来前端打包工具中的后起之秀,重点解决 Webpack 在开发阶段的...【详细内容】
2023-08-21  Search: webpack  点击:(308)  评论:(0)  加入收藏
Bun 的新 Bundler:比 webpack 快 220 倍?
Bun的快速原生捆绑器现在处于测试阶段。它可以通过 bun build CLI 命令或新的 Bun.build() JavaScript API 使用。 从头开始捆绑 10 份三份.js副本,带有源映射和缩小 使用...【详细内容】
2023-05-25  Search: webpack  点击:(373)  评论:(0)  加入收藏
Webpack4编译阶段的性能优化和踩坑
Hello,大家好,我是松宝写代码,写宝写的不止是代码。接下来给大家带来的是关于Webpack4的性能优化的系列,今天带来的是编译阶段的性能优化。由于优化都是在 Webpack 4 上做的,当时...【详细内容】
2023-04-27  Search: webpack  点击:(340)  评论:(0)  加入收藏
前端面试题-工程化-webpack 编译流程
webpack 编译流程 初始化参数:从配置文件和 Shell 语句中读取并合并参数,得出最终的配置对象 用上一步得到的参数初始化 Compiler 对象 加载所有配置的插件 执行对象的 run...【详细内容】
2023-03-23  Search: webpack  点击:(131)  评论:(0)  加入收藏
用webpack创建vue项目/脚手架
图片来源于网络;如有侵权请联系删除对于创建vue项目有很多种方法;但是我觉得这种是相对比较方便的;不用去配置打包啊一些配置了;而且操作也是非常简单;只需要几步就可以了;现在我...【详细内容】
2020-12-28  Search: webpack  点击:(607)  评论:(0)  加入收藏
webpack教程:如何从头开始设置 webpack 5
webpack 对我来说曾经是一个怪物般存在一样,因为它有太多太多的配置项,相反,使用像create-react-app脚手架可以很轻松创建项目,所以有一段时间内,我会尽量避免使用 webpack,因为它...【详细内容】
2020-11-17  Search: webpack  点击:(317)  评论:(0)  加入收藏
想了解Webpack,看这篇就够了
摘要:Webpack是一种前端资源构建工具,一个静态模块打包器。1. 摘要Webpack是一种前端资源构建工具,一个静态模块打包器。在Webpack看来,前端的所有资源文件(js/json/css/img/les...【详细内容】
2020-11-06  Search: webpack  点击:(276)  评论:(0)  加入收藏
揭秘webpack插件工作流程和原理
通过插件我们可以扩展webpack,在合适的时机通过Webpack提供的 API 改变输出结果,使webpack可以执行更广泛的任务,拥有更强的构建能力。 本文将尝试探索 webpack 插件的工作流程,进而去揭秘它的工作原理。同时需要你对webp...【详细内容】
2020-05-20  Search: webpack  点击:(332)  评论:(0)  加入收藏
前端 Webpack 工程化的最佳实践
回想在2015-2016年的时候,开发者们开始渐渐把视线从大量使用Task Runner的Grunt工具,转移到Gulp这种Pipeline形式的工具。Gulp还可以配合上众多个性化插件(如gulp-streamify),从而使得整个前端的准备工作链路,变得清晰易控,...【详细内容】
2020-03-11  Search: webpack  点击:(320)  评论:(0)  加入收藏
入浅出篇-webpack
webpack简介: webpack 是一个前端资源加载/打包工具。 他将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源为什么使用webpack: 转换Es6语法 ...【详细内容】
2019-10-21  Search: webpack  点击:(534)  评论:(0)  加入收藏
▌简易百科推荐
20k级别前端是怎么使用LocalStorage的,想知道吗?
当咱们把咱们想缓存的东西,存在localStorage、sessionStorage中,在开发过程中,确实有利于咱们的开发,咱们想看的时候也是一目了然,点击Application就可以看到。前言大家好,我是林...【详细内容】
2024-03-26  前端之神  微信公众号  Tags:前端   点击:(12)  评论:(0)  加入收藏
前端不存在了?盲测64%的人更喜欢GPT-4V的设计,杨笛一等团队新作
3 月 9 日央视的一档节目上,百度创始人、董事长兼 CEO 李彦宏指出,以后不会存在「程序员」这种职业了,因为只要会说话,人人都会具备程序员的能力。「未来的编程语言只会剩下两种...【详细内容】
2024-03-11  机器之心Pro    Tags:前端   点击:(9)  评论:(0)  加入收藏
前端开始“锈化”?Vue团队开源JS打包工具:基于Rust、速度极快、尤雨溪主导
Vue 团队已正式开源Rolldown &mdash;&mdash; 基于 Rust 的 JavaScrip 打包工具。Rolldown 是使用 Rust 开发的 Rollup 替代品,它提供与 Rollup 兼容的应用程序接口和插件接口...【详细内容】
2024-03-09  OSC开源社区    Tags:Vue   点击:(11)  评论:(0)  加入收藏
两年前端经验还不会手写Promise?
什么是promise?当我们处理异步操作时,我们经常需要进行一系列的操作,如请求数据、处理数据、渲染UI等。在过去,这些操作通常通过回调函数来处理,但是回调函数嵌套过多会导致代码...【详细内容】
2024-03-07  海燕技术栈  微信公众号  Tags:Promise   点击:(23)  评论:(0)  加入收藏
网站开发中的前端和后端开发有什么区别
前端开发和后端开发都是干什么的?有哪些区别?通俗地讲,前端干的工作是用户可以直接看得见的,而后端开发的工作主要在服务端,用户不太能直接看到。虽然前端开发和后端开发的工作有...【详细内容】
2024-02-21  CarryData    Tags:前端   点击:(32)  评论:(0)  加入收藏
网站程序开发中的前后端分离技术
随着互联网的快速发展和技术的不断创新,传统的网站开发模式已经难以满足日益增长的业务需求。为了提高开发效率、增强系统的可维护性和可扩展性,前后端分离技术逐渐成为了网站...【详细内容】
2024-01-31  网站建设派迪星航    Tags:前后端分离   点击:(23)  评论:(0)  加入收藏
如何优雅的实现前端国际化?
JavaScript 中每个常见问题都有许多成熟的解决方案。当然,国际化 (i18n) 也不例外,有很多成熟的 JavaScript i18n 库可供选择,下面就来分享一些热门的前端国际化库!i18nexti18ne...【详细内容】
2024-01-17  前端充电宝  微信公众号  Tags:前端   点击:(69)  评论:(0)  加入收藏
Vue中Scope是怎么做样式隔离的?
scope样式隔离在 Vue 中,样式隔离是通过 scoped 特性实现的。当在一个组件的 <style> 标签上添加 scoped 特性时,Vue 会自动为这个样式块中的所有选择器添加一个唯一的属性,以...【详细内容】
2024-01-04  海燕技术栈  微信公众号  Tags:Vue   点击:(80)  评论:(0)  加入收藏
vue3中 ref和 reactive的区别 ?
最近有朋友在面试过程中经常被问到这么一个问题,vue3 中的ref 和 reactive的区别在哪里,为什么 要定义两个API 一个 api不能实现 响应式更新吗??带着这个疑问 ,我们 接下来进行逐...【详细内容】
2024-01-03  互联网高级架构师  今日头条  Tags:vue3   点击:(38)  评论:(0)  加入收藏
React18 与 Vue3 全方面对比
1. 编程风格 & 视图风格1.1 编程风格 React 语法少、难度大;Vue 语法多,难度小例如指令:Vue<input v-model="username"/><ul> <li v-for="(item,index) in list" :key="inde...【详细内容】
2024-01-03  爱做梦的程序员  今日头条  Tags:Vue3   点击:(72)  评论:(0)  加入收藏
站内最新
站内热门
站内头条