当前位置: 首页 > news >正文

前端工程化之自动化构建

自动化构建

  • 自动化构建的基本知识
    • 历史
    • 云构建 和 自动化构建 的区别:
    • 部署
    • 环境:
    • 构建:
    • 构建产物
    • 构建和打包的性能优化
      • 页面加载优化
      • 构建速度优化
    • DevOps
      • 原则
      • 反馈的技术实践
    • encode-bundle
      • package.json解读
      • src/cli-default.ts
      • src/cli-node.ts
      • src/cli-main.ts
      • src/index.ts
      • src/rollup.ts
      • src/esbuild/index.ts
      • src/plugins/es5.ts
      • src/plugins.ts

主要内容:

  • 自动化构建的介绍
    • DevOps
    • 发展历史
  • 打包工具:encode-bundle(vite:esbuild和swc实现)

自动化构建的基本知识

历史

  1. 2004年前,是没有前端构建的,当时,一般 php 写前端后端的,切图的方式+css 组织起来
    gmail,google doc,共享文档开始
    gmail出现之前,使用邮件客户端集成自己的项目
    引入gmail后,开始做好用的网页,然后网页变得越来越复杂
    直接使用编写的 HTML/JS/CSS,写什么样,浏览器就运行什么样
  2. 框架出现后,整个应用文件很多后,涉及到将文件合并,压缩,混淆等各种各样操作后,这时候,构建工具出现了
    当年的构建工具,gulp / grunt / webpack / vite 打包工具出现
    发布:本地手动构建 -> 上传到服务器 / cdn 上去
    shell脚本实现,七牛cdn,提供命令行工具,access token,secrete token,本地路径,远程路径等写到配置文件中,通过执行七牛的cdn工具就可以把 文件 从本地打包成dist文件传到服务器上去,来实现半自动化操作。
  3. 自动化的打包构建:jenkins(针对前后端的打包构建工具,插件实现构建过程)
  4. 云构建:github action / gitlab / 内部的打包构建平台(一般基于gitlab生态做二次开发的)
    配置 yaml 文件,布置 流水线

云构建 和 自动化构建 的区别:

Docker & VM 有很大关系
Docker:
借助linux特性,像namespace来实现物理隔离的
整体上还是 linux 上一个一个的进程
优势:快速启动,物理机的损耗小
利用类似 git 来实现的
Dockerfile:每个配置就是一层,层和层之间是相互独立的
通过 Dockerfile 可以快速构建一个容器
VM:
裸机上的物理隔离,两个虚拟机之间是完全隔离的
物理机的损耗大,由于要模拟整套硬件

部署

部署文件、npm包、docker镜像
复制文件,发布文件,npm包别人可以复用

环境:

(1)本地环境local:做开发,联调用的
(2)测试环境test:联调完了,发布到测试环境上让测试,产品看一看
(3)预发布环境pre/灰度环境:发布前与正式环境相同的环境,甚至可以被一些用户访问到,给预发布环境导入5%的流量/拉白名单
(4)正式环境 prod
(5)AB环境:测试某些新功能,性能优化的程度,新环境与老环境都运行,导入不同的流量进入,分析数据。前端页面优化,分别给不同用户看不同的页面,分析用户留痕,页面效率,性能等进行对比

构建:

(1)loader:解析,编译,类型处理。处理通道,一个loader处理完后,给下一个loader接着处理,还可以做 同步/异步 操作。webpack可以动态的加载和执行loader。一般是处理文件
(2)plugin:提供了一种类似钩子的功能,在特定时机执行plugin,各种不同的时机执行各种操作,也是可以 同步/异步的。一方面可以处理文件,另一方面可以做各种其他的事情,例如:文件转换等。
(3)plugin和loader的区分:plugin更广泛一些,但是,执行同一个配置文件的构建流程,plugin运行时机,还有数量等各种操作是固定的loader是不一定的
例如,针对没有scss文件,即使写了scss-loader,但是也是不会执行的。但是在插件中,写了 scss-plugin,是肯定要执行的,除非要自己判断。
loader是串联使用的,处理的是一个链条,一个loader的输出是另一个loader的输入。可以理解为搞翻译的,webpack不认识的语言,都可以编写对应的loader帮助webpack翻译成AST的,通过AST做各种各样的转换
plugin可以自定义各种执行时机,特定操作:压缩代码,输入输出目录清理,生成额外文件等都比较适合plugin来做
plugin可以分多个阶段来触发,设定不同的时期触发同一个plugin,但是loader从生命周期开始到生命周期结束,一次性就完成了

构建产物

构建产物一定是浏览器能够看懂的代码
es5,js,css这些

构建和打包的性能优化

面试题:
前端性能优化分很多方面,那么在构建打包流程时候怎么做性能优化?

页面加载优化

  • tree shaking:将所编写的代码生成AST,通过AST的结果进行检查,发现哪些代码虽然你引用了,但是你没用到

为什么esmodule方式可以在构建的时候就能知道哪些代码没用呢?
因为 import 是静态的操作,在编写的时候就已经确定的知道哪些代码要被舍,哪些代码不被舍。
比如,在写JS代码的时候,已经有一些规则,import语句必须写头部,一开始就知道当前的文件需要哪些模块

  • esm / commonJS 多使用 esm,减少使用commonJS
    commonJS和ESM的区别
  1. commonJS:使用require方式引入,require是一种函数
    ecma中是没有 require的,esm中 import 的关键字是从语法方面,JS引擎方面进行执行

为什么vite项目启动快?
因为借助了esm, no-bundle的方式实现的,vite借助浏览器能够直接使用import关键字实现的

  1. 模块加载的时机:commonJS 是在使用的时候才会加载,esm是在编译的阶段就会加载
  2. 导出:commonJS导出时候,基本类型的时候是拷贝,引用类型的时候是地址,esm统一是地址
  3. 动态加载:commonJS中的require是可以动态加载的,import关键字是不能动态加载的,import函数是动态加载的(import 关键字:import xxx from xxx;import 函数:import { xxx } from xxx)
  4. commonJS里两个文件是不能相互引用的,esm中是不存在这种情况
  5. require是不能在浏览器中直接执行的,可以执行的情况:umd,cmd支持require;import是可以在浏览器中直接执行的
  • 按需加载
  • 异步组件
  • vender.js 第三方的依赖
    vue/echarts 这种一般是不会变的,将这些单独打成一个包,在浏览器存储起来,下次使用缓存调用,只有业务变动的通过文件加载上去
    微前端是怎么提升性能的?不同的应用直接是可以共享依赖的,就是类似 vender 概念的。

以上基本都是页面加载的优化,那么有什么办法能够提升构建速度呢?

构建速度优化

  • 空间换时间:将能够缓存下来的东西,不要重复计算,存到内存中
  • cache:打包结果存成单独的文件,下次构建时候,先将这种文件利用起来。就类似:rspack,用的就是这种方法。只有在modules中文件变化,才会重新编译生成出来
  • 利用CPU的多核性能:happypack,esbuild,利用子进程实现快速跑起来。

DevOps

将我们的发布,部署变得稳定有效
除了日常开发需求工作外,都叫 DevOps
工程,规范,流程都属于DevOps
像tslint,tsconfig,webpack.config这种都可以叫做 DevOps,因为将我们的代码满足规范,生成出满足特定要求的代码

原则

流动原则:其实就是加速开发
反馈:一旦有什么问题要快速解决掉,将这种解决的过程变成可重复的模式,变成一种规范
持续的改进和迭代:不是一成不变的,是不停的根据开发的情况,系统整体的变更情况,生产环境的情况等不断变化

反馈的技术实践

建立所谓的遥测系统:追踪,指标,日志
追踪:发布流程的追踪,用户的追踪。比如,用户出现一个bug,自己复现不好复现。借助用户追踪还原现场,实现对bug的快速定位
指标:对前端来说就是性能指标,LCP:页面最长加载时间
日志:错误日志,网络请求错误,JS错误

投简历最好在早上投递,每天最好投递几十个。每天:八股文+常见前端算法题+写代码题:promise,并发控制,发布订阅写一写。一般上半年行情比下半年行情好,这几年没有前几年行情好,一面的面试官很可能是你的同事,面试的时候不要有情绪,一上来就给hard难度,是不想要你,但是一开始是简单的,后来给hard难度的,证明面试官看好你

encode-bundle

打包工具

当执行这个命令安装项目的时候
pnpm i encode-bundle -D
就会生成可执行文件链接,之后在命令行输出"encode-bundle"时候,就会执行 bin 下encode-bundle对应的路径文件

tests 测试目录:encode日常运行的参数/命令做的测试

package.json解读

{"name": "encode-bundle",//命令行工具"version": "1.4.1","main": "dist/index.js", //默认入口,是在构建完后才会存在的"bin": {"encode-bundle": "dist/cli-default.js", //encode-bundle命令执行的文件"encode-bundle-node": "dist/cli-node.js" //encode-bundle-node命令执行的文件},"types": "dist/index.d.ts",//描述文件//发布到npm registry时的目录"files": ["/dist","/assets","/schema.json","README.md"],"keywords": ["encode","bundle","esbuild","swc"],"scripts": {"preinstall": "npx only-allow pnpm", //npm i 之前执行的命令,npx是直接执行一个only-allow仓库的代码,找到注册的机构,执行only-allow下的bin的命令,传递了pnpm参数,然后限制只有pnpm可以安装"prepare": "husky install", //commit的前置钩子"init": "pnpm install && (cd docs && pnpm install)",//pnpm安装并且进入到docs目录下,也执行了pnpm安装"dev": "pnpm run build:fast --watch","dev:docs": "cd docs && pnpm run start","start": "pnpm run dev","start:docs": "pnpm run dev:docs","build": "encode-bundle src/cli-*.ts src/index.ts src/rollup.ts --clean --splitting",//实现了自启动,用的encode-bundle开发的encode-bundle,执行的src目录下的所有cli开头的ts文件,index.ts文件,src目录下的rollup.ts文件都作为entry,--clean清除dist目录下的文件,--splitting拆分,拆分成多个文件"build:docs": "cd docs && pnpm run build",//生成站点"build:fast": "npm run build --no-dts",//--no-dts不生成描述文件"prepublishOnly": "pnpm run build",//发布前执行build"pub:beta": "pnpm -r publish --tag beta",//发布到npm registry时,指定beta标签"pub": "pnpm -r publish",//发布正式版"test": "pnpm run build && pnpm run testOnly","testOnly": "vitest run","encode-fe-lint-scan": "encode-fe-lint scan","encode-fe-lint-fix": "encode-fe-lint fix"},"dependencies": {"bundle-require": "^4.0.0","cac": "^6.7.12",//做命令行的"chokidar": "^3.5.1","debug": "^4.3.1","esbuild": "^0.18.2",//golang写的构建工具,但是有些功能是没有的"execa": "^5.0.0","globby": "^11.0.3","joycon": "^3.0.1","postcss-load-config": "^4.0.1","resolve-from": "^5.0.0","rollup": "^3.2.5","semver": "^7.5.4","source-map": "0.8.0-beta.0","sucrase": "^3.20.3","tree-kill": "^1.2.2"},"devDependencies": {"@rollup/plugin-json": "5.0.1","@swc/core": "1.2.218",//替代babel,rust写的"@types/debug": "4.1.7","@types/flat": "5.0.2","@types/fs-extra": "9.0.13","@types/node": "14.18.12","@types/resolve": "1.20.1","@vitest/runner": "^0.34.3","colorette": "2.0.16","consola": "2.15.3","encode-bundle": "^0.1.0","encode-fe-lint": "^1.0.3","flat": "5.0.2","fs-extra": "10.0.0","husky": "^8.0.0","postcss": "8.4.12","postcss-simple-vars": "6.0.3","resolve": "1.20.0","rollup-plugin-dts": "5.3.0","rollup-plugin-hashbang": "3.0.0","sass": "1.62.1","strip-json-comments": "4.0.0","svelte": "3.46.4","svelte-preprocess": "5.0.3","terser": "^5.16.0","ts-essentials": "9.1.2","tsconfig-paths": "3.12.0","typescript": "5.0.2","vitest": "^0.34.3","wait-for-expect": "3.0.2"},"peerDependencies": {"@swc/core": "^1","postcss": "^8.4.12","typescript": ">=4.1.0"},"peerDependenciesMeta": {"typescript": {"optional": true},"postcss": {"optional": true},"@swc/core": {"optional": true}},"engines": {"node": ">=16.14"},"packageManager": "pnpm@8.6.0","husky": {"hooks": {"pre-commit": "encode-fe-lint commit-file-scan","commit-msg": "encode-fe-lint commit-msg-scan"}}
}

src/cli-default.ts

#!/usr/bin/env node
import { handleError } from './errors'
import { main } from './cli-main'main().catch(handleError)

cli-main的影射

src/cli-node.ts

#!/usr/bin/env node
import { handleError } from './errors'
import { main } from './cli-main'main({skipNodeModulesBundle: true,//将node_modules中有的不打到包的,只打手动编写的代码
}).catch(handleError)

src/cli-main.ts

import { cac } from 'cac'
import flat from 'flat'
import { Format, Options } from '.'
import { version } from '../package.json'
import { slash } from './utils'function ensureArray(input: string): string[] {return Array.isArray(input) ? input : input.split(',')
}export async function main(options: Options = {}) {const cli = cac('encode-bundle')cli.command('[...files]', 'Bundle files', {ignoreOptionDefaultValue: true,}).option('--entry.* <file>', 'Use a key-value pair as entry files').option('-d, --out-dir <dir>', 'Output directory', { default: 'dist' }).option('--format <format>', 'Bundle format, "cjs", "iife", "esm"', {default: 'cjs',}).option('--minify [terser]', 'Minify bundle').option('--minify-whitespace', 'Minify whitespace').option('--minify-identifiers', 'Minify identifiers').option('--minify-syntax', 'Minify syntax').option('--keep-names','Keep original function and class names in minified code').option('--target <target>', 'Bundle target, "es20XX" or "esnext"', {default: 'es2017',}).option('--legacy-output','Output different formats to different folder instead of using different extensions').option('--dts [entry]', 'Generate declaration file').option('--dts-resolve', 'Resolve externals types used for d.ts files').option('--dts-only', 'Emit declaration files only').option('--sourcemap [inline]','Generate external sourcemap, or inline source: --sourcemap inline').option('--watch [path]','Watch mode, if path is not specified, it watches the current folder ".". Repeat "--watch" for more than one path').option('--ignore-watch <path>', 'Ignore custom paths in watch mode').option('--onSuccess <command>','Execute command after successful build, specially useful for watch mode').option('--env.* <value>', 'Define compile-time env variables').option('--inject <file>','Replace a global variable with an import from another file').option('--define.* <value>', 'Define compile-time constants').option('--external <name>','Mark specific packages / package.json (dependencies and peerDependencies) as external').option('--global-name <name>', 'Global variable name for iife format').option('--jsxFactory <jsxFactory>', 'Name of JSX factory function', {default: 'React.createElement',}).option('--jsxFragment <jsxFragment>', 'Name of JSX fragment function', {default: 'React.Fragment',}).option('--replaceNodeEnv', 'Replace process.env.NODE_ENV').option('--no-splitting', 'Disable code splitting').option('--clean', 'Clean output directory').option('--silent','Suppress non-error logs (excluding "onSuccess" process output)').option('--pure <express>', 'Mark specific expressions as pure').option('--metafile', 'Emit esbuild metafile (a JSON file)').option('--platform <platform>', 'Target platform', {default: 'node',}).option('--loader <ext=loader>', 'Specify the loader for a file extension').option('--tsconfig <filename>', 'Use a custom tsconfig').option('--config <filename>', 'Use a custom config file').option('--no-config', 'Disable config file').option('--shims', 'Enable cjs and esm shims').option('--inject-style', 'Inject style tag to document head').option('--treeshake [strategy]','Using Rollup for treeshaking instead, "recommended" or "smallest" or "safest"').option('--publicDir [dir]', 'Copy public directory to output directory').option('--killSignal <signal>','Signal to kill child process, "SIGTERM" or "SIGKILL"').option('--cjsInterop', 'Enable cjs interop')// files:文件列表,flags:各种配置.action(async (files: string[], flags) => {const { build } = await import('.') //从index中引入build方法//将main函数的参数和命令行的参数合并Object.assign(options, { //options是main函数传进来的参数...flags,})// 如果没有entry,并且有files,就将files赋值给entryif (!options.entry && files.length > 0) {options.entry = files.map(slash)}//以下是各种处理参数的逻辑if (flags.format) {const format = ensureArray(flags.format) as Format[]options.format = format}if (flags.external) {const external = ensureArray(flags.external)options.external = external}if (flags.target) {options.target =flags.target.indexOf(',') >= 0? flags.target.split(','): flags.target}if (flags.dts || flags.dtsResolve || flags.dtsOnly) {options.dts = {}if (typeof flags.dts === 'string') {options.dts.entry = flags.dts}if (flags.dtsResolve) {options.dts.resolve = flags.dtsResolve}if (flags.dtsOnly) {options.dts.only = true}}if (flags.inject) {const inject = ensureArray(flags.inject)options.inject = inject}if (flags.define) {const define: Record<string, string> = flat(flags.define)options.define = define}if (flags.loader) {const loader = ensureArray(flags.loader)options.loader = loader.reduce((result, item) => {const parts = item.split('=')return {...result,[parts[0]]: parts[1],}}, {})}// 最终运行build函数,并将options传给它await build(options)})cli.help()cli.version(version)cli.parse(process.argv, { run: false })await cli.runMatchedCommand()
}

src/index.ts

export async function build(_options: Options) {// 分析configconst config =_options.config === false? {}: await loadEncodeBundleConfig(process.cwd(),_options.config === true ? undefined : _options.config,);const configData = typeof config.data === 'function' ? await config.data(_options) : config.data;// 整个build里所有做的事情await Promise.all([...(Array.isArray(configData) ? configData : [configData])].map(async (item) => {const logger = createLogger(item?.name);const options = await normalizeOptions(logger, item, _options);logger.info('CLI', `encode-bundle v${version}`);if (config.path) {logger.info('CLI', `Using encode-bundle config: ${config.path}`);}if (options.watch) {logger.info('CLI', 'Running in watch mode');}// 生成描述文件const dtsTask = async () => {if (options.dts) {await new Promise<void>((resolve, reject) => {// _dirname:当前文件所在目录的绝对路径const worker = new Worker(path.join(__dirname, './rollup.js')); //运行一个worker子进程,执行rollup.js文件// worker.postMessage:向子进程发送消息,也就是给rollup传递参数worker.postMessage({configName: item?.name,options: {...options, // functions cannot be clonedbanner: undefined,footer: undefined,esbuildPlugins: undefined,esbuildOptions: undefined,plugins: undefined,treeshake: undefined,onSuccess: undefined,outExtension: undefined,},});// 抛出message事件,监听子进程的消息worker.on('message', (data) => {if (data === 'error') {reject(new Error('error occured in dts build'));} else if (data === 'success') {resolve();} else {const { type, text } = data;if (type === 'log') {console.log(text);} else if (type === 'error') {console.error(text);}}});});}};// 生成真正的文件const mainTasks = async () => {if (!options.dts?.only) {let onSuccessProcess: ChildProcess | undefined;let onSuccessCleanup: (() => any) | undefined | void;/** Files imported by the entry */const buildDependencies: Set<string> = new Set();let depsHash = await getAllDepsHash(process.cwd());const doOnSuccessCleanup = async () => {if (onSuccessProcess) {await killProcess({pid: onSuccessProcess.pid,signal: options.killSignal || 'SIGTERM',});} else if (onSuccessCleanup) {await onSuccessCleanup();}// reset them in all occasions anywayonSuccessProcess = undefined;onSuccessCleanup = undefined;};const debouncedBuildAll = debouncePromise(() => {return buildAll();},100,handleError,);const buildAll = async () => {await doOnSuccessCleanup();// Store previous build dependencies in case the build failed// So we can restore itconst previousBuildDependencies = new Set(buildDependencies);buildDependencies.clear();if (options.clean) {const extraPatterns = Array.isArray(options.clean) ? options.clean : [];// .d.ts files are removed in the `dtsTask` instead// `dtsTask` is a separate process, which might start before `mainTasks`if (options.dts) {extraPatterns.unshift('!**/*.d.{ts,cts,mts}');}//清理旧文件await removeFiles(['**/*', ...extraPatterns], options.outDir);logger.info('CLI', 'Cleaning output folder');}const css: Map<string, string> = new Map();await Promise.all([...options.format.map(async (format, index) => {//pluginContainer插件管理器,将插件列表一个一个给到,它就可以按顺序在特定的时机按照定义一个一个执行我们的插件const pluginContainer = new PluginContainer([shebang(),...(options.plugins || []),treeShakingPlugin({treeshake: options.treeshake,name: options.globalName,silent: options.silent,}),cjsSplitting(),cjsInterop(),//重点看怎么生成es5的es5(),sizeReporter(),terserPlugin({minifyOptions: options.minify,format,terserOptions: options.terserOptions,globalName: options.globalName,logger,}),]);//执行runEsbuild函数await runEsbuild(options, {pluginContainer,format,css: index === 0 || options.injectStyle ? css : undefined,logger,buildDependencies,}).catch((error) => {previousBuildDependencies.forEach((v) => buildDependencies.add(v));throw error;});}),]);if (options.onSuccess) {if (typeof options.onSuccess === 'function') {onSuccessCleanup = await options.onSuccess();} else {onSuccessProcess = execa(options.onSuccess, {shell: true,stdio: 'inherit',});onSuccessProcess.on('exit', (code) => {if (code && code !== 0) {process.exitCode = code;}});}}};const startWatcher = async () => {if (!options.watch) return;const { watch } = await import('chokidar');const customIgnores = options.ignoreWatch? Array.isArray(options.ignoreWatch)? options.ignoreWatch: [options.ignoreWatch]: [];const ignored = ['**/{.git,node_modules}/**', options.outDir, ...customIgnores];const watchPaths =typeof options.watch === 'boolean'? '.': Array.isArray(options.watch)? options.watch.filter((path): path is string => typeof path === 'string'): options.watch;logger.info('CLI',`Watching for changes in ${Array.isArray(watchPaths)? watchPaths.map((v) => '"' + v + '"').join(' | '): '"' + watchPaths + '"'}`,);logger.info('CLI',`Ignoring changes in ${ignored.map((v) => '"' + v + '"').join(' | ')}`,);const watcher = watch(watchPaths, {ignoreInitial: true,ignorePermissionErrors: true,ignored,});watcher.on('all', async (type, file) => {file = slash(file);if (options.publicDir && isInPublicDir(options.publicDir, file)) {logger.info('CLI', `Change in public dir: ${file}`);copyPublicDir(options.publicDir, options.outDir);return;}// By default we only rebuild when imported files change// If you specify custom `watch`, a string or multiple strings// We rebuild when those files changelet shouldSkipChange = false;if (options.watch === true) {if (file === 'package.json' && !buildDependencies.has(file)) {const currentHash = await getAllDepsHash(process.cwd());shouldSkipChange = currentHash === depsHash;depsHash = currentHash;} else if (!buildDependencies.has(file)) {shouldSkipChange = true;}}if (shouldSkipChange) {return;}logger.info('CLI', `Change detected: ${type} ${file}`);debouncedBuildAll();});};logger.info('CLI', `Target: ${options.target}`);await buildAll();copyPublicDir(options.publicDir, options.outDir);startWatcher();}};await Promise.all([dtsTask(), mainTasks()]);}),);
}

src/rollup.ts

import { parentPort } from 'worker_threads';
import { InputOptions, OutputOptions, Plugin } from 'rollup';
import { NormalizedOptions } from './';
import ts from 'typescript';
import hashbangPlugin from 'rollup-plugin-hashbang';
import jsonPlugin from '@rollup/plugin-json';
import { handleError } from './errors';
import { defaultOutExtension, removeFiles } from './utils';
import { TsResolveOptions, tsResolvePlugin } from './rollup/ts-resolve';
import { createLogger, setSilent } from './log';
import { getProductionDeps, loadPkg } from './load';
import path from 'path';
import { reportSize } from './lib/report-size';
import resolveFrom from 'resolve-from';const logger = createLogger();const parseCompilerOptions = (compilerOptions?: any) => {if (!compilerOptions) return {};const { options } = ts.parseJsonConfigFileContent({ compilerOptions }, ts.sys, './');return options;
};const dtsPlugin: typeof import('rollup-plugin-dts') = require('rollup-plugin-dts');type RollupConfig = {inputConfig: InputOptions;outputConfig: OutputOptions[];
};const findLowestCommonAncestor = (filepaths: string[]) => {if (filepaths.length <= 1) return '';const [first, ...rest] = filepaths;let ancestor = first.split('/');for (const filepath of rest) {const directories = filepath.split('/', ancestor.length);let index = 0;for (const directory of directories) {if (directory === ancestor[index]) {index += 1;} else {ancestor = ancestor.slice(0, index);break;}}ancestor = ancestor.slice(0, index);}return ancestor.length <= 1 && ancestor[0] === '' ? '/' + ancestor[0] : ancestor.join('/');
};const toObjectEntry = (entry: string[]) => {entry = entry.map((e) => e.replace(/\\/g, '/'));const ancestor = findLowestCommonAncestor(entry);return entry.reduce((result, item) => {const key = item.replace(ancestor, '').replace(/^\//, '').replace(/\.[a-z]+$/, '');return {...result,[key]: item,};}, {});
};const getRollupConfig = async (options: NormalizedOptions): Promise<RollupConfig> => {setSilent(options.silent);const compilerOptions = parseCompilerOptions(options.dts?.compilerOptions);const dtsOptions = options.dts || {};dtsOptions.entry = dtsOptions.entry || options.entry;if (Array.isArray(dtsOptions.entry) && dtsOptions.entry.length > 1) {dtsOptions.entry = toObjectEntry(dtsOptions.entry);}let tsResolveOptions: TsResolveOptions | undefined;if (dtsOptions.resolve) {tsResolveOptions = {};// Only resolve specific types when `dts.resolve` is an arrayif (Array.isArray(dtsOptions.resolve)) {tsResolveOptions.resolveOnly = dtsOptions.resolve;}// `paths` should be handled by rollup-plugin-dtsif (compilerOptions.paths) {const res = Object.keys(compilerOptions.paths).map((p) => new RegExp(`^${p.replace('*', '.+')}$`),);tsResolveOptions.ignore = (source) => {return res.some((re) => re.test(source));};}}const pkg = await loadPkg(process.cwd());const deps = await getProductionDeps(process.cwd());const encodeBundleCleanPlugin: Plugin = {name: 'encode-bundle:clean',async buildStart() {if (options.clean) {await removeFiles(['**/*.d.{ts,mts,cts}'], options.outDir);}},};const ignoreFiles: Plugin = {name: 'encode-bundle:ignore-files',load(id) {if (!/\.(js|cjs|mjs|jsx|ts|tsx|mts|json)$/.test(id)) {return '';}},};const fixCjsExport: Plugin = {name: 'encode-bundle:fix-cjs-export',renderChunk(code, info) {if (info.type !== 'chunk' ||!/\.(ts|cts)$/.test(info.fileName) ||!info.isEntry ||info.exports?.length !== 1 ||info.exports[0] !== 'default')return;return code.replace(/(?<=(?<=[;}]|^)\s*export\s*){\s*([\w$]+)\s*as\s+default\s*}/, `= $1`);},};return {inputConfig: {input: dtsOptions.entry,onwarn(warning, handler) {if (warning.code === 'UNRESOLVED_IMPORT' ||warning.code === 'CIRCULAR_DEPENDENCY' ||warning.code === 'EMPTY_BUNDLE') {return;}return handler(warning);},plugins: [encodeBundleCleanPlugin,tsResolveOptions && tsResolvePlugin(tsResolveOptions),hashbangPlugin(),jsonPlugin(),ignoreFiles,dtsPlugin.default({tsconfig: options.tsconfig,compilerOptions: {...compilerOptions,baseUrl: compilerOptions.baseUrl || '.',// Ensure ".d.ts" modules are generateddeclaration: true,// Skip ".js" generationnoEmit: false,emitDeclarationOnly: true,// Skip code generation when error occursnoEmitOnError: true,// Avoid extra workcheckJs: false,declarationMap: false,skipLibCheck: true,preserveSymlinks: false,// Ensure we can parse the latest codetarget: ts.ScriptTarget.ESNext,},}),].filter(Boolean),external: [// Exclude dependencies, e.g. `lodash`, `lodash/get`...deps.map((dep) => new RegExp(`^${dep}($|\\/|\\\\)`)),...(options.external || []),],},outputConfig: options.format.map((format): OutputOptions => {const outputExtension =options.outExtension?.({ format, options, pkgType: pkg.type }).dts ||defaultOutExtension({ format, pkgType: pkg.type }).dts;return {dir: options.outDir || 'dist',format: 'esm',exports: 'named',banner: dtsOptions.banner,footer: dtsOptions.footer,entryFileNames: `[name]${outputExtension}`,plugins: [format === 'cjs' && options.cjsInterop && fixCjsExport].filter(Boolean),};}),};
};async function runRollup(options: RollupConfig) {const { rollup } = await import('rollup');try {const start = Date.now();const getDuration = () => {return `${Math.floor(Date.now() - start)}ms`;};logger.info('dts', 'Build start');// 生成dts文件const bundle = await rollup(options.inputConfig);const results = await Promise.all(options.outputConfig.map(bundle.write));const outputs = results.flatMap((result) => result.output);logger.success('dts', `⚡️ Build success in ${getDuration()}`);reportSize(logger,'dts',outputs.reduce((res, info) => {const name = path.relative(process.cwd(),path.join(options.outputConfig[0].dir || '.', info.fileName),);return {...res,[name]: info.type === 'chunk' ? info.code.length : info.source.length,};}, {}),);} catch (error) {handleError(error);logger.error('dts', 'Build error');}
}async function watchRollup(options: { inputConfig: InputOptions; outputConfig: OutputOptions[] }) {const { watch } = await import('rollup');watch({...options.inputConfig,plugins: options.inputConfig.plugins,output: options.outputConfig,}).on('event', (event) => {if (event.code === 'START') {logger.info('dts', 'Build start');} else if (event.code === 'BUNDLE_END') {logger.success('dts', `⚡️ Build success in ${event.duration}ms`);parentPort?.postMessage('success');} else if (event.code === 'ERROR') {logger.error('dts', 'Build failed');handleError(event.error);}});
}// 入口函数
const startRollup = async (options: NormalizedOptions) => {// options就是index.ts文件中传递的optionsconst config = await getRollupConfig(options);if (options.watch) {watchRollup(config);} else {try {// 执行runRollup函数await runRollup(config);parentPort?.postMessage('success');} catch (error) {parentPort?.postMessage('error');}parentPort?.close();}
};// 监听父进程传递过来的消息
parentPort?.on('message', (data) => {logger.setName(data.configName);const hasTypescript = resolveFrom.silent(process.cwd(), 'typescript');if (!hasTypescript) {logger.error('dts', `You need to install "typescript" in your project`);parentPort?.postMessage('error');parentPort?.close();return;}startRollup(data.options);
});

src/esbuild/index.ts

import fs from 'fs';
import path from 'path';
import { build as esbuild, BuildResult, formatMessages, Plugin as EsbuildPlugin } from 'esbuild';
import { NormalizedOptions, Format } from '..';
import { getProductionDeps, loadPkg } from '../load';
import { Logger, getSilent } from '../log';
import { nodeProtocolPlugin } from './node-protocol';
import { externalPlugin } from './external';
import { postcssPlugin } from './postcss';
import { sveltePlugin } from './svelte';
import consola from 'consola';
import { defaultOutExtension, truthy } from '../utils';
import { swcPlugin } from './swc';
import { nativeNodeModulesPlugin } from './native-node-module';
import { PluginContainer } from '../plugin';
import { OutExtensionFactory } from '../options';const getOutputExtensionMap = (options: NormalizedOptions,format: Format,pkgType: string | undefined,
) => {const outExtension: OutExtensionFactory = options.outExtension || defaultOutExtension;const defaultExtension = defaultOutExtension({ format, pkgType });const extension = outExtension({ options, format, pkgType });return {'.js': extension.js || defaultExtension.js,};
};/*** Support to exclude special package.json*/
const generateExternal = async (external: (string | RegExp)[]) => {const result: (string | RegExp)[] = [];for (const item of external) {if (typeof item !== 'string' || !item.endsWith('package.json')) {result.push(item);continue;}let pkgPath: string = path.isAbsolute(item)? path.dirname(item): path.dirname(path.resolve(process.cwd(), item));const deps = await getProductionDeps(pkgPath);result.push(...deps);}return result;
};export async function runEsbuild(options: NormalizedOptions,{format,css,logger,buildDependencies,pluginContainer,}: {format: Format;css?: Map<string, string>;buildDependencies: Set<string>;logger: Logger;pluginContainer: PluginContainer;},
) {const pkg = await loadPkg(process.cwd());const deps = await getProductionDeps(process.cwd());// 哪些东西是不需要打包进来的const external = [// Exclude dependencies, e.g. `lodash`, `lodash/get`...deps.map((dep) => new RegExp(`^${dep}($|\\/|\\\\)`)),...(await generateExternal(options.external || [])),];const outDir = options.outDir;const outExtension = getOutputExtensionMap(options, format, pkg.type);const env: { [k: string]: string } = {...options.env,};if (options.replaceNodeEnv) {env.NODE_ENV = options.minify || options.minifyWhitespace ? 'production' : 'development';}logger.info(format, 'Build start');const startTime = Date.now();let result: BuildResult | undefined;const splitting =format === 'iife'? false: typeof options.splitting === 'boolean'? options.splitting: format === 'esm';const platform = options.platform || 'node';const loader = options.loader || {};const injectShims = options.shims;// 设置上下文,变量,日志打印工具pluginContainer.setContext({format,splitting,options,logger,});// pluginContainer在特定的时机会触发,在某个时机要做某个事情await pluginContainer.buildStarted();const esbuildPlugins: Array<EsbuildPlugin | false | undefined> = [format === 'cjs' && nodeProtocolPlugin(),{name: 'modify-options',setup(build) {pluginContainer.modifyEsbuildOptions(build.initialOptions);if (options.esbuildOptions) {options.esbuildOptions(build.initialOptions, { format });}},},// esbuild's `external` option doesn't support RegExp// So here we use a custom plugin to implement itformat !== 'iife' &&externalPlugin({external,noExternal: options.noExternal,skipNodeModulesBundle: options.skipNodeModulesBundle,tsconfigResolvePaths: options.tsconfigResolvePaths,}),options.tsconfigDecoratorMetadata && swcPlugin({ logger }),nativeNodeModulesPlugin(),postcssPlugin({css,inject: options.injectStyle,cssLoader: loader['.css'],}),sveltePlugin({ css }),...(options.esbuildPlugins || []),];const banner = typeof options.banner === 'function' ? options.banner({ format }) : options.banner;const footer = typeof options.footer === 'function' ? options.footer({ format }) : options.footer;try {// esbuild来自于第三方esbuild,入口是一个JS文件,但是也只是收集参数生成配置,最终将参数给到go编译出来的exe文件result = await esbuild({entryPoints: options.entry,format: (format === 'cjs' && splitting) || options.treeshake ? 'esm' : format,bundle: typeof options.bundle === 'undefined' ? true : options.bundle,platform,globalName: options.globalName,jsxFactory: options.jsxFactory,jsxFragment: options.jsxFragment,sourcemap: options.sourcemap ? 'external' : false,target: options.target,banner,footer,tsconfig: options.tsconfig,loader: {'.aac': 'file','.css': 'file','.eot': 'file','.flac': 'file','.gif': 'file','.jpeg': 'file','.jpg': 'file','.mp3': 'file','.mp4': 'file','.ogg': 'file','.otf': 'file','.png': 'file','.svg': 'file','.ttf': 'file','.wav': 'file','.webm': 'file','.webp': 'file','.woff': 'file','.woff2': 'file',...loader,},mainFields: platform === 'node' ? ['module', 'main'] : ['browser', 'module', 'main'],plugins: esbuildPlugins.filter(truthy),define: {ENCODE_BUNDLE_FORMAT: JSON.stringify(format),...(format === 'cjs' && injectShims? {'import.meta.url': 'importMetaUrl',}: {}),...options.define,...Object.keys(env).reduce((res, key) => {const value = JSON.stringify(env[key]);return {...res,[`process.env.${key}`]: value,[`import.meta.env.${key}`]: value,};}, {}),},inject: [format === 'cjs' && injectShims ? path.join(__dirname, '../assets/cjs_shims.js') : '',format === 'esm' && injectShims && platform === 'node'? path.join(__dirname, '../assets/esm_shims.js'): '',...(options.inject || []),].filter(Boolean),outdir: options.legacyOutput && format !== 'cjs' ? path.join(outDir, format) : outDir,outExtension: options.legacyOutput ? undefined : outExtension,write: false,splitting,logLevel: 'error',minify: options.minify === 'terser' ? false : options.minify,minifyWhitespace: options.minifyWhitespace,minifyIdentifiers: options.minifyIdentifiers,minifySyntax: options.minifySyntax,keepNames: options.keepNames,pure: typeof options.pure === 'string' ? [options.pure] : options.pure,metafile: true,});} catch (error) {logger.error(format, 'Build failed');throw error;}if (result && result.warnings && !getSilent()) {const messages = result.warnings.filter((warning) => {if (warning.text.includes(`This call to "require" will not be bundled because`) ||warning.text.includes(`Indirect calls to "require" will not be bundled`))return false;return true;});const formatted = await formatMessages(messages, {kind: 'warning',color: true,});formatted.forEach((message) => {consola.warn(message);});}// Manually write filesif (result && result.outputFiles) {await pluginContainer.buildFinished({outputFiles: result.outputFiles,metafile: result.metafile,});const timeInMs = Date.now() - startTime;logger.success(format, `⚡️ Build success in ${Math.floor(timeInMs)}ms`);}if (result.metafile) {for (const file of Object.keys(result.metafile.inputs)) {buildDependencies.add(file);}if (options.metafile) {const outPath = path.resolve(outDir, `metafile-${format}.json`);await fs.promises.mkdir(path.dirname(outPath), { recursive: true });await fs.promises.writeFile(outPath, JSON.stringify(result.metafile), 'utf8');}}
}

src/plugins/es5.ts

import { PrettyError } from '../errors'
import { Plugin } from '../plugin'
import { localRequire } from '../utils'// 返回是Plugin类型
export const es5 = (): Plugin => {let enabled = false// 返回的是一个对象return {name: 'es5-target',// 这里有esbuildOptions,renderChunk两个特定方法,就会传给PluginContainer,插件管理器就会在特定的时机触发运行这些函数esbuildOptions(options) {if (options.target === 'es5') {options.target = 'es2020'enabled = true}},async renderChunk(code, info) {if (!enabled || !/\.(cjs|js)$/.test(info.path)) {return}// 寻找swcconst swc: typeof import('@swc/core') = localRequire('@swc/core')if (!swc) {throw new PrettyError('@swc/core is required for es5 target. Please install it with `npm install @swc/core -D`')}// 调用swc的transform方法const result = await swc.transform(code, {filename: info.path,sourceMaps: this.options.sourcemap,minify: Boolean(this.options.minify),jsc: {target: 'es5',parser: {syntax: 'ecmascript',},minify: this.options.minify === true ? {compress: false,mangle: {reserved: this.options.globalName ? [this.options.globalName] : []},} : undefined,},})return {code: result.code,map: result.map,}},}
}

encode-bundle做的事情其实就是规范化的整理各种各样的参数,打造了一个自己的插件机制

src/plugins.ts

像webpack和vite是怎样去做插件管理的,也能加深了前面 loaders 和 plugins 的区别

export class PluginContainer {plugins: Plugin[]context?: PluginContextconstructor(plugins: Plugin[]) {this.plugins = plugins}setContext(context: PluginContext) {this.context = context}getContext() {if (!this.context) throw new Error(`Plugin context is not set`)return this.context}modifyEsbuildOptions(options: EsbuildOptions) {for (const plugin of this.plugins) {if (plugin.esbuildOptions) {plugin.esbuildOptions.call(this.getContext(), options)}}}async buildStarted() {for (const plugin of this.plugins) {if (plugin.buildStart) {await plugin.buildStart.call(this.getContext())}}}async buildFinished({outputFiles,metafile,}: {outputFiles: OutputFile[]metafile?: Metafile}) {const files: Array<ChunkInfo | AssetInfo> = outputFiles.filter((file) => !file.path.endsWith('.map')).map((file): ChunkInfo | AssetInfo => {if (isJS(file.path) || isCSS(file.path)) {const relativePath = path.relative(process.cwd(), file.path)const meta = metafile?.outputs[relativePath]return {type: 'chunk',path: file.path,code: file.text,map: outputFiles.find((f) => f.path === `${file.path}.map`)?.text,entryPoint: meta?.entryPoint,exports: meta?.exports,imports: meta?.imports,}} else {return { type: 'asset', path: file.path, contents: file.contents }}})const writtenFiles: WrittenFile[] = []await Promise.all(files.map(async (info) => {for (const plugin of this.plugins) {if (info.type === 'chunk' && plugin.renderChunk) {const result = await plugin.renderChunk.call(this.getContext(),info.code,info)if (result) {info.code = result.codeif (result.map) {const originalConsumer = await new SourceMapConsumer(parseSourceMap(info.map))const newConsumer = await new SourceMapConsumer(parseSourceMap(result.map))const generator =SourceMapGenerator.fromSourceMap(originalConsumer)generator.applySourceMap(newConsumer, info.path)info.map = generator.toJSON()originalConsumer.destroy()newConsumer.destroy()}}}}const inlineSourceMap = this.context!.options.sourcemap === 'inline'const contents =info.type === 'chunk'? info.code +getSourcemapComment(inlineSourceMap,info.map,info.path,isCSS(info.path)): info.contentsawait outputFile(info.path, contents, {mode: info.type === 'chunk' ? info.mode : undefined,})writtenFiles.push({get name() {return path.relative(process.cwd(), info.path)},get size() {return contents.length},})if (info.type === 'chunk' && info.map && !inlineSourceMap) {const map =typeof info.map === 'string' ? JSON.parse(info.map) : info.mapconst outPath = `${info.path}.map`const contents = JSON.stringify(map)await outputFile(outPath, contents)writtenFiles.push({get name() {return path.relative(process.cwd(), outPath)},get size() {return contents.length},})}}))for (const plugin of this.plugins) {if (plugin.buildEnd) {await plugin.buildEnd.call(this.getContext(), { writtenFiles })}}}
}

相关文章:

前端工程化之自动化构建

自动化构建 自动化构建的基本知识历史云构建 和 自动化构建 的区别&#xff1a;部署环境&#xff1a;构建&#xff1a;构建产物构建和打包的性能优化页面加载优化构建速度优化 DevOps原则反馈的技术实践 encode-bundlepackage.json解读src/cli-default.tssrc/cli-node.tssrc/cl…...

camx的xml解析

ls out/target/product/<product>/gen/STATIC_LIBRARIES/libcamxgenerated_intermediates/generated g_chromatix g_facedetection g_parser g_sensorg_chromatix/ tuning相关xml的解析codeg_facedetection/ 人脸检测相关xml的解析codeg_parser/ 主要的解析manager 流…...

虚幻引擎 Anim To Tex| RVT | RT

本文上篇分为4个部分&#xff1a;动画驱动材质&#xff0c;虚拟纹理&#xff0c;Rendertarget&#xff0c;以及其他杂项的地编ta干货整理。&#xff08;其中RT部分基本为UOD重要截图摘录&#xff09; 本文下篇为&#xff1a;skylight和directional light的区别&#xff0c;未完…...

计算机视觉与深度学习 | 钢筋捆数识别

===================================================== github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 ===================================================== 钢筋捆数 1、初始结果2、处理效果不佳时的改进方法‌1、预处理增强2、后…...

关于PHP开源CMS系统ModStart的详细介绍及使用指南

关于PHP开源CMS系统ModStart的详细介绍及使用指南&#xff1a; &#x1f50d; ModStart是什么&#xff1f; 基于Laravel框架开发的模块化CMS系统采用Apache 2.0 开源协议&#xff0c;完全免费可商用特别适合需要快速搭建企业级网站/管理系统的开发者 &#x1f680; 核心优势…...

VMware vCenter Server 安全漏洞升级方案一则

一、安全漏洞情况 根据VMware提供的安全建议&#xff08;VMSA-024-0012&#xff09;&#xff0c;VMware vCenter Server可能经受以下漏洞的威胁&#xff1a; 漏洞一为VMware vCenter Server堆溢出漏洞&#xff08;CVE-2024-37079&#xff0c;CVE-2024-37080&#xff09;&…...

Linux服务之网络共享

目录 一.存储类型 二.NFS 2.1定义 2.2工作原理 2.3优势 2.4NFS工具 2.4.1exportfs 2.4.2showmount 2.5NFS相关软件及命令 2.6模拟实现NFS 准备工作&#xff08;服务端和客户端都需要&#xff09; 服务端位置 客户端配置 测试 补充&#xff1a;设置自动挂载 一.存…...

接口幂等性问题

幂等性问题出现在创建和更新数据时&#xff1a; 一、创建 1、在创建数据时&#xff0c;数据库方面&#xff0c;创建有效的唯一索引&#xff0c;用来数据兜底&#xff0c;并在程序中做异常捕获。 2、在插入数据时可以创建一个防重表做过滤&#xff0c;如果防重数据比较小又需…...

LeetCode每日一题4.14

1534. 统计好三元组 问题分析 遍历数组&#xff0c;满足好三元组定义&#xff0c;count1 思路 枚举i&#xff0c;j&#xff0c;k 代码 class Solution:def countGoodTriplets(self, arr: List[int], a: int, b: int, c: int) -> int:n len(arr)count 0for i in range…...

活动安排问题 之 前缀和与差分

文章目录 D. Robert Hood and Mrs Hood 考虑到一个活动开始时间和结束时间s,e&#xff0c;那么可以影响到的范围就是 s-d1,e,所以我们只需对这个每一个活动可以影响到的区域进行标记即可&#xff0c;当然为了降低时间复杂度&#xff0c;我们将使用前缀和与差分 t int(input()…...

HTTP 和 HTTPS 协议的区别及使用场景

在互联网的世界里,HTTP 和 HTTPS 是我们经常接触到的两种网络协议,它们在数据传输、安全性等方面存在诸多差异,适用的场景也各有不同。​ 一、HTTP 和 HTTPS 的基本概念​ HTTP,即超文本传输协议(Hyper - Text Transfer Protocol),是一种用于分布式、协作式和超媒体信息…...

SAP 供应链:采购订单ME21N创建关键点

一、ME21N创建采购订单关键点 采购组织/采购组 字段&#xff1a;EKORG&#xff08;采购组织&#xff09;、EKGRP&#xff08;采购组&#xff09;关键点&#xff1a;采购组织必须与公司代码&#xff08;Company Code&#xff09;关联&#xff0c;采购组对应采购员职责范围示例&…...

重构无人机动力控制范式:Breeze 55A FOC 电调技术深度测评 ——全新Vfast 观测器如何突破效率与精度双重瓶颈

一、引言 在无人机动力系统中&#xff0c;电调&#xff08;电子调速器&#xff09;作为连接电池与电机的核心枢纽&#xff0c;其控制精度、效率及可靠性直接影响飞行性能。南昌长空科技的Breeze 55A FOC 电调凭借全新 Vfast 观测器技术与成熟的 FOC&#xff08;矢量控制&#…...

LLM做逻辑推理题-哪一项圈出后不用找零

题目:   某天&#xff0c;两男两女走进一家自助餐厅&#xff0c;每人从机器上取下一许如下图所示的标价单。   50、95   45、90   40、85   35、80   30、75   25、70   20、65   15、60   10、55   &#xff08;1&#xff09;四人要同样的食品&#xf…...

第十章 json操作

第十章 json操作 文章目录 第十章 json操作一、Marshal 序列化二、Unmarshal 反序列化1 已知数据解析2 未知数据解析3 json测试 一、Marshal 序列化 package mainimport ("encoding/json""fmt" ) type Animal struct {Name string json:"name"…...

Python-Django集成yolov识别模型摄像头人数监控网页前后端分离

程序示例精选 Python-Django集成yolov识别模型摄像头人数监控网页前后端分离 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《Python-Django集成yolov识别模型摄像头人数监控网页前后端分离…...

「出海匠」借助CloudPilot AI实现AWS降本60%,支撑AI电商高速增长

&#x1f50e;公司简介 「出海匠」&#xff08;chuhaijiang.com&#xff09;是「数绘星云」公司打造的社交内容电商服务平台&#xff0c;专注于为跨境生态参与者提供数据支持与智能化工作流。平台基于大数据与 AI 技术&#xff0c;帮助商家精准分析市场趋势、优化运营策略&…...

tsconfig.json配置不生效

说明一下我遇到的问题&#xff0c;这是我的配置文件代码的 {"compilerOptions": {"module": "none","target": "ES5","outFile": "./dist/bundle.js"} } 和我想象不同的是&#xff0c;我编译成 js 没…...

WebFlux应用中获取x-www-form-urlencoded数据的六种方法

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…...

GPT4O画图玩法案例,不降智,非dalle

网址如下&#xff1a; 玩法1&#xff1a;吉卜力&#xff08;最火爆&#xff09; 提示词&#xff1a;请将附件图片转化为「吉卜力」风格&#xff0c;尺寸不变 玩法2&#xff1a;真人绘制 提示词&#xff1a;创作一张图片&#xff0c;比例4:3&#xff0c;一个20岁的中国女孩…...

【Python爬虫】简单案例介绍1

目录 三、Python爬虫的简单案例 3.1 网页分析 单页 三、Python爬虫的简单案例 本节以科普中国网站为例。 3.1 网页分析 单页 在运用 Python 进行爬虫开发时&#xff0c;一套严谨且有序的流程是确保数据获取高效、准确的关键。首先&#xff0c;深入分析单个页面的页面结构…...

【CAPL实战:以太网】MAC地址由整数形式转换为字符串形式的自定义函数

我在文章MAC地址在字符串形式、数字形式和byte数组中的转换中讲过MAC地址在字符串形式、数字形式和byte数组中的转换方法和思想。如果你仔细阅读过这篇文章,那么MAC地址的形式要如何转换,自定义函数要如何实现它肯定也能信手拈来。如果你还不会也没有关系,今天我们尝试用另一…...

#4 我们为什么使用物联网? 以及 物联网的整体结构

设备不物联是否可以&#xff1f; 答案 是可以的&#xff0c;从项目实战的角度&#xff0c;还是有很多包括分拣&#xff0c;控制&#xff0c;检测等应用是分立的&#xff0c;这个和成本&#xff0c;场景&#xff0c;客户接受度等因素有关。 局部看&#xff0c;一些系统的确很简…...

MQTT、HTTP短轮询、HTTP长轮询、WebSocket

一、协议“明星定位”仿写 MQTT&#xff1a;物联网领域的**“明星协议”**&#xff0c;专为低带宽、高延迟网络环境下的设备通信而生。HTTP短轮询&#xff1a;数据拉取界的**“劳模”**&#xff0c;用简单粗暴的频繁请求换取数据更新。HTTP长轮询&#xff1a;短轮询的**“智能…...

Apache Commons CLI 入门教程:轻松解析命令行参数

文章目录 Apache Commons CLI 入门教程&#xff1a;轻松解析命令行参数一、什么是 Commons CLI&#xff1f;二、为什么选择 Commons CLI&#xff1f;三、快速开始1. 添加依赖2. 基础示例3. 运行示例1. 在Idea中运行2. 命令行中运行3. 使用 Maven/Gradle 运行&#xff08;推荐&a…...

Kubernetes Operator 是什么,以及它们的用途

最近一个朋友问我关于EKS的升级问题&#xff1a; 场景&#xff1a; 如果我有 100 个 EKS 集群需要升级&#xff0c;那么所有集群都安装了安全插件。由于我不想在升级后手动在每个EKS中重复安装此插件&#xff0c;如何实现EKS升级后自动安装这个安全插件&#xff1f; 答案有多…...

SAP ABAP语言中的比较运算符

一、基本比较运算符 ​运算符描述​​关键字形式​​符号形式​​示例​等于EQIF a EQ b 或 IF a b不等于NE<>IF a NE b 或 IF a <> b大于GT>IF a GT b 或 IF a > b小于LT<IF a LT b 或 IF a < b大于等于GE❌ 不支持IF a GE b小于等于LE❌ 不支持IF …...

10秒调用大模型!思源笔记+Ollama实现实时AI推理助力写作效率提升

文章目录 前言1. 下载运行Ollama框架2. Ollama下载大语言模型3. 思源笔记设置连接Ollama4. 测试笔记智能辅助写作5. 安装Cpolar工具6. 配置Ollama公网地址7. 笔记设置远程连接Ollama8. 固定Ollama公网地址 推荐 ​ 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂…...

Linux网络DFS共享服务搭建

目录 一.存储类型 1.DAS优势和局限性 2.SAN的特点及组成 3.NAS优势与局限性 二.NFS服务 1.NFS工作原理 2.NFS工具 2.1 exportfs 2.2 showmount 3.实际操作 3.1.服务器操作 3.2.客户机操作 3.3.默认无法写操作 一.存储类型 存储类型分为三种 直连式存储&#xff1a…...

汇舟问卷:国外问卷调查项目

这个项目现在市面上主要有三种玩法&#xff0c;我给你整点实在的&#xff1a; 第一种&#xff1a;上网站直接做调查&#xff08;站点查&#xff09; ​和国内调查网站差不多&#xff0c;国外也有一堆调查网站。可以直接到国外问卷网站注册账号答题。 好处是题目现成的不用自…...

JSON-RPC 2.0 vs REST API 详细对比分析

现在要开始做一个新的业务模块了&#xff0c;系统思考下 新的业务模式应该是采用 JSON-RPC 2.0 还是 老套路 REST API 的接口协议 &#xff0c;系统的学习下 1. 基本概念 JSON-RPC 2.0 无状态的、轻量级的远程过程调用&#xff08;RPC&#xff09;协议使用 JSON 作为数据格式…...

Python 类方法

Python 类方法示例 类方法是绑定到类而不是实例的方法&#xff0c;它们使用 classmethod 装饰器定义&#xff0c;第一个参数通常是 cls&#xff08;表示类本身&#xff09;。下面是一个具体的例子&#xff1a; class Employee:"""员工类"""rais…...

MVC流程讲解——以文件下载为例

整体的流程是这样&#xff1a; 用户点击一个树节点 → 请求远程机器该目录下的文件信息 → 显示在树控件和列表控件中。 &#x1f9f1; MCV 模式简介&#xff08;针对这个场景&#xff09; 模块代表什么主要职责Model&#xff08;模型&#xff09;数据结构和逻辑表示你传输的…...

深度学习之线性代数基础

2.3.7 点积 ∑按位积 2.3.8 矩阵-向量积 2.3.9 矩阵-矩阵乘法 2.3.10 范数...

某公司网络OSPF单区域配置

1.配置背景&#xff1a; xx公司网络由三台路由器和一台交换机组成&#xff0c;现在想要三台路由器之间通过OSPF实现互连互通。 2.网络结构如下&#xff1a; 3.具体配置&#xff1a; 3.1路由器 RA 配置&#xff1a; 1.更改主机名称&#xff1a; Router>en Router#conf t…...

vue+flask+GNN+neo4j图书知识图谱推荐系统

文章结尾部分有CSDN官方提供的学长 联系方式名片 文章结尾部分有CSDN官方提供的学长 联系方式名片 关注B站&#xff0c;有好处&#xff01; 编号: F025 pro 架构: vueflaskneo4jmysqlpytorch 亮点&#xff1a;两种基于知识图谱的推荐算法&#xff08;GNN和基于路径推荐&#x…...

小程序页面传值的多种方式

开发小程序&#xff0c;总是避免不了页面和页面之间数据共享&#xff0c;实现方法有很多种&#xff0c;以下就讲解一下小程序页面传值&#xff0c;需要的朋友可以参考下。 1 使用wx.navigateTo()传值 这种传值方式有两种&#xff0c; url后面拼接传值&#xff1a;需要跳转的…...

基于SSM框架的校园食堂小程序设计与实现

概述 基于SSM框架开发的微信小程序民大食堂用餐综合服务平台&#xff0c;该系统集成了商家管理、餐品展示、在线点。 主要内容 一、管理员模块功能实现 ​​用户信息管理​​ 管理员可添加、查看和删除用户信息&#xff0c;确保平台用户数据安全可靠。 ​​商家信息管理​​…...

FOC算法对MCU计算资源的需求?

评估FOC(磁场定向控制)算法对MCU计算资源的需求,需从算法复杂度、硬件特性、实时性要求等多维度分析。以下是具体步骤和关键要点: 一、拆解FOC算法的核心模块及计算复杂度 FOC算法主要由以下子模块组成,需分别评估各模块的计算量: 1. 传感器采样与预处理 ADC采样:电流…...

在 Excel 中使用通义灵码辅助开发 VBA 程序

VBA 简介 VBA 是一种用于微软办公套件&#xff08;如 Word、Excel、PowerPoint 等&#xff09;的编程语言&#xff0c;它本质上是一种内嵌的脚本&#xff0c;或者可以认为是一段命令&#xff0c;其标准叫法被称为宏。 VBA 只能依赖于对应的软件进行开发&#xff0c;例如本文就…...

嵌入式基础(三)基础外设

嵌入式基础&#xff08;三&#xff09;基础外设 1.什么是UART&#xff1f;与USART有什么区别⭐⭐⭐ (1)什么是UART 通用异步收发传输器&#xff08;Universal Asynchronous Receiver/Transmitter)&#xff0c;通常称作UART。是一种异步全双工串行通信协议&#xff0c;它将要…...

【微服务管理】深入理解 Gateway 网关:原理与实现

在当今微服务架构盛行的时代&#xff0c;Gateway 网关扮演着举足轻重的角色。它作为微服务架构的重要组成部分&#xff0c;为系统提供了统一的入口&#xff0c;承担着诸如路由转发、负载均衡、安全防护、流量控制等关键功能。本文将深入探讨 Gateway 网关的底层原理&#xff0c…...

AI与无人驾驶汽车:如何通过机器学习提升自动驾驶系统的安全性?

引言 想象一下&#xff0c;在高速公路上&#xff0c;一辆无人驾驶汽车正平稳行驶。突然&#xff0c;前方的车辆紧急刹车&#xff0c;而旁边车道有一辆摩托车正快速接近。在这千钧一发的瞬间&#xff0c;自动驾驶系统迅速分析路况&#xff0c;判断最安全的避险方案&#xff0c;精…...

【网络安全】通过 JS 寻找接口实现权限突破

未经许可,不得转载。 本文所述所有风险点均已修复。 文章目录 引言正文引言 以下些漏洞已被起亚方面修复;起亚方面确认,这些漏洞从未被恶意利用过。 2024年6月11日,我们发现起亚汽车存在一系列严重安全漏洞,攻击者仅凭车牌号即可远程控制车辆的核心功能。该攻击不需要接触…...

【HarmonyOS NEXT】多目标产物构建实践

目录 什么是多产物构建 如何定义多个构建产物 如何在项目中使用 参考文章 什么是多产物构建 在鸿蒙应用开发中&#xff0c;一个应用可定义多个 product&#xff0c;每一个 product 对应一个定制的 APP 包&#xff0c;每个 product 中支持对 bundleName、bundleType、输出产…...

Openlayers:实现聚合

一、什么是聚合 聚合是一种将一定范围内的多个图形合并为一个图形的一种技术方案。通常用来解决图形过于集中或者图形数量过多的问题。 在Openlayers中主要通过Cluster数据源来实现聚合的效果。与其它的数据源不同&#xff0c;Cluster数据源在实例化时需要接收一个矢量数据源…...

相机内外参

文章目录 相机内参相机外参 相机的内外参是相机标定过程中确定的重要参数&#xff0c;用于建立图像像素坐标与实际世界坐标之间的关系。 相机内参 定义&#xff1a;相机内参是描述相机内部光学和几何特性的参数&#xff0c;主要包括焦距、主点坐标、像素尺度因子以及畸变系数等…...

轨道力学课程习题集

轨道力学课程习题集 第一讲&#xff1a;轨道力学概述 思考题 推导开普勒第三定律与牛顿万有引力定律的关系。 计算地球表面的第一宇宙速度和第二宇宙速度。 设计一个太阳同步轨道&#xff0c;高度为800公里&#xff0c;要求当地时间为上午10:30。 分析地球静止轨道的稳定性…...

【Web三十一】K8S的常用命令

01 基础资源操作​​ 查看资源​​ # 查看 kube-system 命名空间下的 Pod 列表 kubectl get pods -n kube-system# 查看节点详细信息&#xff08;IP、状态等&#xff09; kubectl get nodes -o wide# 按标签筛选 Pod&#xff08;例如筛选标签 appnginx 的 Pod&#xff09; ku…...

GDPR/CCPA

定义 GDPR&#xff08;通用数据保护条例&#xff09; 适用范围&#xff1a;适用于欧盟境内所有成员国&#xff0c;以及处理欧盟居民个人数据的全球企业。 数据主体权利&#xff1a;用户有权知道、访问、更正、删除&#xff08;被遗忘权&#xff09;自己的数据&#xff0c;还有…...