phalapi可以依赖注入么_带你了解 snowpack 原理,你还学得动么(上)

phalapi可以依赖注入么_带你了解 snowpack 原理,你还学得动么(上)_第1张图片

作者:AlienZHOU

转发链接:https://zhuanlan.zhihu.com/p/149351900

前言

近期,随着 vue3 的各种曝光,vite 的热度上升,与 vite 类似的 snowpack 的关注度也逐渐增加了。目前(2020.06.18)snowpack 在 Github 上已经有了将近 1w stars。

snowpack 的代码很轻量,本文会从实现原理的角度介绍 snowpack 的特点。同时,带大家一起看看,作为一个以原生 JavaScript 模块化为核心的年轻的构建工具,它是如何实现“老牌”构建工具所提供的那些特性的。

前段时间,小编也发布一篇关于snowpack优质文章《提高10倍打包速度工具Snowpack 2.0正式发布,再也不需要打包器》,非常受老铁们喜欢,甚至还有忠实老粉丝们私信问 snowpack真的有这么厉害么?可否讲讲它的原理?回归正题,今天咱们一起揭开snowpack的神秘面纱。

1. 初始 snowpack

时间拨回到 2019 年上半年,一天中午我百无聊赖地读到了 A Future Without Webpack 这篇文章。通过它了解到了 pika/snowpack 这个项目(当时还叫 pika/web)。

文章的核心观点如下:

在如今(2019年),我们完全可以抛弃打包工具,而直接在浏览器中使用浏览器原生的 JavaScript 模块功能。这主要基于三点考虑:

  1. 兼容性可接受:基本主流的浏览器版本都支持直接使用 JavaScript Module 了(当然,IE 一如既往除外)。
  2. 性能问题的改善:之前打包的一个重要原因是 HTTP/1.1 的特性导致,我们合并请求再优化性能;而如今 HTTP/2 普及之后,这个性能问题不像以前那么突出了。
  3. 打包的必要性:打包工具的存在主要就是为了处理模块化与合并请求,而以上两点基本解决这两个问题;再加之打包工具越来越复杂,此消彼长,其存在的必要性自然被作者所质疑。

由于我认为 webpack 之类的打包工具,“发家”后转型做构建工具并非最优解,实是一种阴差阳错的阶段性成果。所以当时对这个项目提到的观点也很赞同,其中印象最深的当属它提到的:

In 2019, you should use a bundler because you want to, not because you need to.

2. 初窥 snowpack

看到这篇文章后(大概是19年6、7月?),抱着好奇立刻去 Github 上读了这个项目。当时看这个项目的时候大概是 0.4.x 版本,其源码和功能都非常简单。

snowpack 的最初版核心目标就是不再打包业务代码,而是直接使用浏览器原生的 JavaScript Module 能力。

phalapi可以依赖注入么_带你了解 snowpack 原理,你还学得动么(上)_第2张图片

所以从它的处理流程上来看,对业务代码的模块,基本只需要把 ESM 发布(拷贝)到发布目录,再将模块导入路径从源码路径换为发布路径即可。

而对 node_modules 则通过遍历 package.json 中的依赖,按该依赖列表为粒度将 node_modules 中的依赖打包。以 node_modules 中每个包的入口作为打包 entry,使用 rollup 生成对应的 ESM 模块文件,放到 web_modules 目录中,最后替换源码的 import 路径,使得可以通过原生 JavaScript Module 来加载 node_modules 中的包。

- import { createElement, Component } from "preact";- import htm from "htm";+ import { createElement, Component } from "/web_modules/preact.js";+ import htm from "/web_modules/htm.js";

从 v0.4.0 版本的源码可以看出,其初期功能确实非常简单,甚至有些简陋,以至于缺乏很多现代前端开发所需的特性,明显是不能用于生产环境的。

直观感受来说,它当时就欠缺以下能力:

  1. import CSS / image / …:由于 webpack 一切皆模块的理念 + 组件化开发的深入人心,import anything 的书写模式已经深入开发者的观念中。对 CSS 等内容依赖与加载能力的缺失,将成为它的阿克琉斯之踵。
  2. 语法转换能力:作为目标成为构建工具的 snowpack(当时叫 web),并没有能够编译 Typescript、JSX 等语法文件的能力,你当然可以再弄一个和它毫无关系的工具来处理语法,但是,这不就是构建工具应该集成的么?
  3. HMR:这可能不那么要命,但俗话说「由俭入奢易,由奢入俭难」,被“惯坏”开发者们自然会有人抵触这一特性的缺失。
  4. 性能:虽说它指出,上了 HTTP2 后,使用 JavaScript modules 性能并不会差,但毕竟没有实践过,对此还是抱有怀疑。
  5. 环境变量:这虽然是一个小特性,但在我接触过的大多数项目中都会用到它,它可以帮助开发者自动测卸载线上代码中的调试工具,可以根据环境判断,自动将埋点上报到不同的服务上。确实需要一个这样好用的特性。

3. snowpack 的进化

时间回到 2020 年上半年,随着 vue3 的不断曝光,与其有一定关联的另一个项目 vite 也逐渐吸引了人们的目光。而其介绍中提到的 snowpack 也突然吸引到了更多的热度与讨论。当时我只是对 pika 感到熟悉,好奇地点开 snowpack 项目主页的时候,才发现这个一年前初始的项目(pika/web)已经升级到了 pika/snowpack v2。而项目源码也不再是之前那唯一而简单的 index.ts,在核心代码外,还包含了诸多官方插件。

看着已经完全变样的 Readme,我的第一直觉是,之前我想到的那些问题,应该已经有了解决方案。

phalapi可以依赖注入么_带你了解 snowpack 原理,你还学得动么(上)_第3张图片

抱着学习的态度,对它进行重新了解之后,发现果然如此。好奇心趋势我对它的解决方案去一探究竟。

本文写于 2020.06.18,源码基于 [email protected]

3.1. import CSS

import CSS 的问题还有一个更大的范围,就是非 JavaScript 资源的加载,包括图片、JSON 文件、文本等。

先说说 CSS。

import './index.css';

上面这种语法目前浏览是不支持的。所以 snowpack 用了一个和之前 webpack 很类似的方式,将 CSS 文件变为用于注入样式的 JS 模块。如果你熟悉 webpack,肯定知道如果你只是在 loader 中处理 CSS,那么并不会生成单独的 CSS 文件(这就是为什么会有 [mini-css-extract-plugin](https://link.zhihu.com/?target=https%3A//webpack.js.org/plugins/mini-css-extract-plugin/)),而是加载一个 JS 模块,然后在 JS 模块中通过 DOM API 将 CSS 文本作为 style 标签的内容插入到页面中。

为此,snowpack 自己写了一个简单的模板方法,生成将 CSS 样式注入页面的 JS 模块。下面这段代码可以实现样式注入的功能:

const code = '.test { height: 100px }';const styleEl = document.createElement("style");const codeEl = document.createTextNode(code);styleEl.type = 'text/css';styleEl.appendChild(codeEl);document.head.appendChild(styleEl);

可以看到,除了第一行式子的右值,其他都是不变的,因此可以很容易生成一个符合需求的 JS 模块:

const jsContent = `  const code = ${JSON.stringify(code)};  const styleEl = document.createElement("style");  const codeEl = document.createTextNode(code);  styleEl.type = 'text/css';  styleEl.appendChild(codeEl);  document.head.appendChild(styleEl);`;fs.writeFileSync(filename, jsContent);

snowpack 中的实现代码比我们上面多了一些东西,不过与样式注入无关,这个放到后面再说。

通过将 CSS 文件的内容保存到 JS 变量,然后再使用 JS 调用 DOM API 在页面注入 CSS 内容即可使用 JavaScript Modules 的能力加载 CSS。而源码中的 index.css 也会被替换为 index.css.proxy.js:

- import './index.css';+ import './index.css.proxy.js';

proxy 这个名词之后会多次出现,因为为了能够以模块化方式导入非 JS 资源,snowpack 把生成的中间 JavaScript 模块都叫做 proxy。这种实现方式也几乎和 webpack 一脉相承。

3.2. 图片的 import

在目前的前端开发场景中,还有一类非常典型的资源就是图片。

import avatar from './avatar.png';function render() {  return (    
          
  );}

上面代码的书写方式已经普遍应用在很多项目代码中了。那么 snowpack 是怎么处理的呢?

太阳底下没有新鲜事,snowpack 和 webpack 一样,对于代码中导入的 avatar变量,最后其实都是该静态资源的 URI。

我们以 snowpack 提供的官方 React 模板为例来看看图片资源的引入处理。

npx create-snowpack-app snowpack-test --template @snowpack/app-template-react

初始化模版运行后,可以看到源码与构建后的代码差异如下:

- import React, { useState } from 'react';- import logo from './logo.svg';- import './App.css';+ import React, { useState } from '/web_modules/react.js';+ import logo from './logo.svg.proxy.js';+ import './App.css.proxy.js';

与 CSS 类似,也为图片(svg)生成了一个 JS 模块 logo.svg.proxy.js,其模块内容为:

// logo.svg.proxy.jsexport default "/_dist_/logo.svg"; 

套路与 webpack 如出一辙。以 build 命令为例,我们来看一下 snowpack 的处理方式。

首先是将源码中的静态文件(logo.svg)拷贝到发布目录:

allFiles = glob.sync(`**/*`, {  ...});const allBuildNeededFiles: string[] = [];await Promise.all(  allFiles.map(async (f) => {    f = path.resolve(f); // this is necessary since glob.sync() returns paths with / on windows.  path.resolve() will switch them to the native path separator.    ...    return fs.copyFile(f, outPath);  }),);

然后,我们可以看到 snowpack 中的一个叫 transformEsmImports 的关键方法调用。这个方法可以将源码 JS 中 import 的模块路径进行转换。例如对 node_modules 中的导入都替换为 web_modules。在这里对 svg 文件的导入名也会被加上 .proxy.js:

code = await transformEsmImports(code, (spec) => {  ……  if (spec.startsWith('/') || spec.startsWith('./') || spec.startsWith('../')) {    const ext = path.extname(spec).substr(1);    if (!ext) {      ……    }    const extToReplace = srcFileExtensionMapping[ext];    if (extToReplace) {      ……    }    if (spec.endsWith('.module.css')) {      ……    } else if (!isBundled && (extToReplace || ext) !== 'js') {      const resolvedUrl = path.resolve(path.dirname(outPath), spec);      allProxiedFiles.add(resolvedUrl);      spec = spec + '.proxy.js';    }    return spec;  }  ……});

此时,我们的 svg 文件和源码的导入语法(import logo from './logo.svg.proxy.js')均已就绪,最后剩下的就是生成 proxy 文件了。也非常简单:

for (const proxiedFileLoc of allProxiedFiles) {  const proxiedCode = await fs.readFile(proxiedFileLoc, {encoding: 'utf8'});  const proxiedExt = path.extname(proxiedFileLoc);  const proxiedUrl = proxiedFileLoc.substr(buildDirectoryLoc.length);  const proxyCode = wrapEsmProxyResponse({    url: proxiedUrl,    code: proxiedCode,    ext: proxiedExt,    config,  });  const proxyFileLoc = proxiedFileLoc + '.proxy.js';  await fs.writeFile(proxyFileLoc, proxyCode, {encoding: 'utf8'});}

wrapEsmProxyResponse 是一个生成 proxy 模块的方法,目前只处理包括 JSON、image 和其他类型的文件,对于其他类型(包括了图片),就是非常简单的导出 url:

return `export default ${JSON.stringify(url)};`;

所以,对于 CSS 与图片,由于浏览器模块规范均不支持该类型,所以都会转换为 JS 模块,这块 snowpack 和 webpack 实现很类似。

3.3. HMR(热更新)

phalapi可以依赖注入么_带你了解 snowpack 原理,你还学得动么(上)_第4张图片

如果你刚才仔细去看了 wrapEsmProxyResponse 方法,会发现对于 CSS “模块”,它除了有注入 CSS 的功能代码外,还多着这么几行:

import * as __SNOWPACK_HMR_API__ from '/${buildOptions.metaDir}/hmr.js';import.meta.hot = __SNOWPACK_HMR_API__.createHotContext(import.meta.url);import.meta.hot.accept();import.meta.hot.dispose(() => {  document.head.removeChild(styleEl);});

这些代码就是用来实现热更新的,也就是 HMR(Hot Module Replacement)。它使得当一个模块更新时,应用会在前端自动替换该模块,而不需要 reload 整个页面。这对于依赖状态构建的单页应用开发非常友好。

import.meta 是一个包含模块源信息的对象,例如模块自身的 url 就可以在这里面取到。而 HMR 其实和 import.meta 没太大关系,snowpack 只是借用这块地方存储了 HMR 相关功能对象。所以不必过分纠结于它。

我们再来仔细看看上面这段 HMR 的功能代码,API 是不是很熟悉?可下面这段对比一下

import _ from 'lodash';import printMe from './print.js';function component() {  const element = document.createElement('div');  const btn = document.createElement('button');  element.innerHTML = _.join(['Hello', 'webpack'], ' ');  btn.innerHTML = 'Click me and check the console!';  btn.onclick = printMe;  element.appendChild(btn);  return element;}document.body.appendChild(component());+ + if (module.hot) {+   module.hot.accept('./print.js', function() {+     console.log('Accepting the updated printMe module!');+     printMe();+   })+ }

上面的代码取自 webpack 官网上 HMR 功能的使用说明,可见,snowpack 站在“巨人”的肩膀上,沿袭了 webpack 的 API,其原理也极其相似。网上关于 webpack HMR 的讲解文档很多,这里就不细说了,基本的实现原理就是:

  • snowpack 进行构建,并 watch 源码;
  • 在 snowpack 服务端与前端应用间建立 websocket 连接;
  • 当源码变动时,重新构建,完成后通过 websocket 将模块信息(id/url)推送给前端应用;
  • 前端应用监听到这个消息后,根据模块信息加载模块
  • 同时,触发该模块之前注册的回调事件,这个在以上代码中就是传入 accept和 dispose 中的方法

因此,wrapEsmProxyResponse 里构造出的这段代码

import.meta.hot.dispose(() => {  document.head.removeChild(styleEl);});

其实就是表示,当该 CSS 更新并要被替换时,需要移除之前注入的样式。而执行顺序是:远程模块 --> 加载完毕 --> 执行旧模块的 accept 回调 --> 执行旧模块的 dispose 回调。

snowpack 中 HMR 前端核心代码放在了 [assets/hmr.js](https://link.zhihu.com/?target=https%3A//github.com/pikapkg/snowpack/blob/v2.5.1/assets/hmr.js)。代码也非常简短,其中值得一提的是,不像 webpack 使用向页面添加 script 标签来加载新模块,snowpack 直接使用了原生的 dynamic import 再加载新模块:

const [module, ...depModules] = await Promise.all([  import(id + `?mtime=${updateID}`),  ...deps.map((d) => import(d + `?mtime=${updateID}`)),]);

也是秉承了使用浏览器原生 JavaScript Modules 能力的理念。


小憩一下。看完上面的内容,你是不是发现,这些技术方案都和 webpack 的实现非常类似。snowpack 正是借鉴了这些前端开发的优秀实践,而其一开始的理念也很明确:为前端开发提供一个不需要打包器(Bundler)的构建工具。

phalapi可以依赖注入么_带你了解 snowpack 原理,你还学得动么(上)_第5张图片

webpack 的一大知识点就是优化,既包括构建速度的优化,也包括构建产物的优化。其中一个点就是如何拆包。webpack v3 之前有 CommonChunkPlugin,v4 之后通过 SplitChunk 进行配置。使用声明式的配置,比我们人工合包拆包更加“智能”。合并与拆分是为了减少重复代码,同时增加缓存利用率。但如果本身就不打包,自然这两个问题就不再存在。而如果都是直接加载 ESM,那么 Tree-Shaking 的所解决的问题也在一定程度上也被缓解了(当然并未根治)。

再结合最开始提到的性能与兼容性,如果这两个坎确实迈了过去,那我们何必要用一个内部流程复杂、上万行代码的工具来解决一个不再存在的问题呢?

好了,让我们回来继续聊聊 snowpack 里其他特性的实现。


3.4. 环境变量

通过环境来判断是否关闭调试功能是一个非常常见的需求。

if (process.env.NODE_ENV === 'production') {  disableDebug();}

snowpack 中也实现了环境变量的功能。从使用文档上来看,你可以在模块中的 import.meta.env 上取到变量。像下面这么使用:

if (import.meta.env.NODE_ENV === 'production') {  disableDebug();}

那么环境变量是如何被注入进去的呢?

还是以 build 的源码为例,在代码生成的阶段上,通过 [wrapImportMeta 方法的调用](https://zhuanlan.zhihu.com/p/149351900/https%3C/code%3E://github.com/pikapkg/snowpack/blob/v2.5.1/src/commands/build.ts#L346)生成了新的代码段,

code = wrapImportMeta({code, env: true, hmr: false, config});

那么经过 wrapImportMeta 处理后的代码和之前有什么区别呢?答案从源码里就能知晓:

export function wrapImportMeta({  code,  hmr,  env,  config: {buildOptions},}: {  code: string;  hmr: boolean;  env: boolean;  config: SnowpackConfig;}) {  if (!code.includes('import.meta')) {    return code;  }  return (    (hmr      ? `import * as  __SNOWPACK_HMR__ from '/${buildOptions.metaDir}/hmr.js';import.meta.hot = __SNOWPACK_HMR__.createHotContext(import.meta.url);`      : ``) +    (env      ? `import __SNOWPACK_ENV__ from '/${buildOptions.metaDir}/env.js';import.meta.env = __SNOWPACK_ENV__;`      : ``) +    '' +    code  );}

对于包含 import.meta 调用的代码,snowpack 都会在里面注入对 env.js 模块的导入,并将导入值放在 import.meta.env 上。因此构建后的代码会变为:

+ import __SNOWPACK_ENV__ from '/__snowpack__/env.js';+ import.meta.env = __SNOWPACK_ENV__;if (import.meta.env.NODE_ENV === 'production') {  disableDebug();}

如果是在开发环境下,还会加上 env.js 的 HMR。而 env.js 的内容也很简单,就是直接将 env 中的键值作为对象的键值,通过 export default 导出。

默认情况下 env.js 只包含 MODE 和 NODE_ENV 两个值,你可以通过 @snowpack/plugin-dotenv 插件来直接读取 .env 相关文件。

3.5. CSS Modules 的支持

CSS 的模块化一直是一个难题,其一个重要的目的就是做 CSS 样式的隔离。常用的解决方案包括:

  • 使用 BEM 这样的命名方式
  • 使用 webpack 提供的 CSS Module 功能
  • 使用 styled components 这样的 CSS in JS 方案
  • shadow dom 的方案

我之前的文章详细介绍了这几类方案。snowpack 也提供了类似 webpack 中的 CSS Modules 功能。

import styles from './index.module.css';function render() {  return 
Hello world!
;}
phalapi可以依赖注入么_带你了解 snowpack 原理,你还学得动么(上)_第6张图片

而在 snowpack 中启用 CSS Module 必须要以 .module.css 结尾,只有这样才会将文件特殊处理:

if (spec.endsWith('.module.css')) {  const resolvedUrl = path.resolve(path.dirname(outPath), spec);  allCssModules.add(resolvedUrl);  spec = spec.replace('.module.css', '.css.module.js');}

而所有 CSS Module 都会经过 wrapCssModuleResponse 方法的包装,其主要作用就是将生成的唯一 class 名的 token 注入到文件内,并作为 default 导出:

_cssModuleLoader = _cssModuleLoader || new (require('css-modules-loader-core'))();const {injectableSource, exportTokens} = await _cssModuleLoader.load(code, url, undefined, () => {  throw new Error('Imports in CSS Modules are not yet supported.');});return `  ……  export let code = ${JSON.stringify(injectableSource)};  let json = ${JSON.stringify(exportTokens)};  export default json;  ……`;

本篇未完结,请见下一篇

推荐JavaScript学习相关文章

《使用Canvas实现一个在线发牌游戏「附源码」》

《基于Canvas实现的高斯模糊(上)「JS篇」》

《基于Canvas实现的高斯模糊(下)「JS篇」》

《由浅入深,66条JavaScript面试知识点(一)》

《由浅入深,66条JavaScript面试知识点(二)》

《由浅入深,66条JavaScript面试知识点(三)》

《由浅入深,66条JavaScript面试知识点(四)》

《由浅入深,66条JavaScript面试知识点(五)》

《由浅入深,66条JavaScript面试知识点(六)》

《由浅入深,66条JavaScript面试知识点(七)》

《为什么 setTimeout 有最小时延 4ms ?》

《如何处理 Node.js 中出现的未捕获异常?》

《Angular v10.0.0 正式发布,不再支持 IE9/10》

《基于 Docker 的 SSR 持续开发集成环境实践》

《细聊图解webpack 指南手册》

《一文带你彻底搞懂 NPM 知识点「进阶篇」》

《细聊webpack性能优化面面观》

《JS实现各种日期操作方法汇总》

《「实践」细聊前端性能优化总结》

《「实践」浏览器中的画中画(Picture-in-Picture)模式及其 API》

《「多图」一文带你彻底搞懂 Web Workers (上)》

《「多图」一文带你彻底搞懂 Web Workers (中)》

《深入细聊前端下载总结「干货」》

《细品西瓜播放器功能分析(上)「实践」》

《细品西瓜播放器功能分析(下)「实践」》

《细聊50道JavaScript基础面试题「附答案」》

《webpack4主流程源码解说以及动手实现一个简单的webpack(上)》

《webpack4主流程源码解说以及动手实现一个简单的webpack(下)》

《细聊前端架构师的视野》

《细聊应用场景再谈防抖和节流「进阶篇」》

《前端埋点统一接入方案实践》

《细聊微内核架构在前端的应用「干货」》

《一种高性能的Tree组件实现方案「干货」》

《进击的JAMStack》

《前后端全部用 JS 开发是什么体验(Hybrid + Egg.js经验分享)上》

《前后端全部用 JS 开发是什么体验(Hybrid + Egg.js经验分享)中》

《前后端全部用 JS 开发是什么体验(Hybrid + Egg.js经验分享)下》

《一文带你搞懂 babel-plugin-import 插件(上)「源码解析」》

《一文带你搞懂 babel-plugin-import 插件(下)「源码解析」》

《JavaScript常用API合集汇总「值得收藏」》

《推荐10个常用的图片处理小帮手(上)「值得收藏」》

《推荐10个常用的图片处理小帮手(下)「值得收藏」》

《JavaScript 中ES6代理的实际用例》

《12 个实用的前端开发技巧总结》

《一文带你搞懂搭建企业级的 npm 私有仓库》

《教你如何使用内联框架元素 IFrames 的沙箱属性提高安全性?》

《细说前端开发UI公共组件的新认识「实践」》

《细说DOM API中append和appendChild的三个不同点》

《细品淘系大佬讲前端新人如何上王者「干货」》

《一文带你彻底解决背景跟随弹窗滚动问题「干货」》

《推荐常用的5款代码比较工具「值得收藏」》

《Node.js实现将文字与图片合成技巧》

《爱奇艺云剪辑Web端的技术实现》

《我再也不敢说我会写前端 Button组件「实践」》

《NodeX Component - 滴滴集团 Node.js 生态组件体系「实践」》

《Node Buffers 完整指南》

《推荐18个webpack精美插件「干货」》

《前端开发需要了解常用7种JavaScript设计模式》

《浅谈浏览器架构、单线程js、事件循环、消息队列、宏任务和微任务》

《了不起的 Webpack HMR 学习指南(上)「含源码讲解」》

《了不起的 Webpack HMR 学习指南(下)「含源码讲解」》

《10个打开了我新世界大门的 WebAPI(上)「实践」》

《10个打开了我新世界大门的 WebAPI(中)「实践」》

《10个打开了我新世界大门的 WebAPI(下)「实践」》

《「图文」ESLint 在中大型团队的应用实践》

《Deno是代码的浏览器,你认同吗?》

《前端存储除了 localStorage 还有啥?》

《Javascript 多线程编程​的前世今生》

《微前端方案 qiankun(实践及总结)》

《「图文」V8 垃圾回收原来这么简单?》

《Webpack 5模块联邦引发微前端的革命?》

《基于 Web 端的人脸识别身份验证「实践」》

《「前端进阶」高性能渲染十万条数据(时间分片)》

《「前端进阶」高性能渲染十万条数据(虚拟列表)》

《图解 Promise 实现原理(一):基础实现》

《图解 Promise 实现原理(二):Promise 链式调用》

《图解 Promise 实现原理(三):Promise 原型方法实现》

《图解 Promise 实现原理(四):Promise 静态方法实现》

《实践教你从零构建前端 Lint 工作流「干货」》

《高性能多级多选级联组件开发「JS篇」》

《深入浅出讲解Node.js CLI 工具最佳实战》

《延迟加载图像以提高Web网站性能的五种方法「实践」》

《比较 JavaScript 对象的四种方式「实践」》

《使用Service Worker让你的 Web 应用如虎添翼(上)「干货」》

《使用Service Worker让你的 Web 应用如虎添翼(中)「干货」》

《使用Service Worker让你的 Web 应用如虎添翼(下)「干货」》

《前端如何一次性处理10万条数据「进阶篇」》

《推荐三款正则可视化工具「JS篇」》

《如何让用户选择是否离开当前页面?「JS篇」》

《JavaScript开发人员更喜欢Deno的五大原因》

《仅用18行JavaScript实现一个倒数计时器》

《图文细说JavaScript 的运行机制》

《一个轻量级 JavaScript 全文搜索库,轻松实现站内离线搜索》

《推荐Web程序员常用的15个源代码编辑器》

《10个实用的JS技巧「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(一)「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(二)「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(三)「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(四)「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(五)「值得收藏」》

《细品269个JavaScript小函数,让你少加班熬夜(六)「值得收藏」》

《深入JavaScript教你内存泄漏如何防范》

《手把手教你7个有趣的JavaScript 项目-上「附源码」》

《手把手教你7个有趣的JavaScript 项目-下「附源码」》

《JavaScript 使用 mediaDevices API 访问摄像头自拍》

《手把手教你前端代码如何做错误上报「JS篇」》

《一文让你彻底搞懂移动前端和Web 前端区别在哪里》

《63个JavaScript 正则大礼包「值得收藏」》

《提高你的 JavaScript 技能10 个问答题》

《JavaScript图表库的5个首选》

《一文彻底搞懂JavaScript 中Object.freeze与Object.seal的用法》

《可视化的 JS:动态图演示 - 事件循环 Event Loop的过程》

《教你如何用动态规划和贪心算法实现前端瀑布流布局「实践」》

《可视化的 js:动态图演示 Promises & Async/Await 的过程》

《原生JS封装拖动验证滑块你会吗?「实践」》

《如何实现高性能的在线 PDF 预览》

《细说使用字体库加密数据-仿58同城》

《Node.js要完了吗?》

《Pug 3.0.0正式发布,不再支持 Node.js 6/8》

《纯JS手写轮播图(代码逻辑清晰,通俗易懂)》

《JavaScript 20 年 中文版之创立标准》

《值得收藏的前端常用60余种工具方法「JS篇」》

《箭头函数和常规函数之间的 5 个区别》

《通过发布/订阅的设计模式搞懂 Node.js 核心模块 Events》

《「前端篇」不再为正则烦恼》

《「速围」Node.js V14.3.0 发布支持顶级 Await 和 REPL 增强功能》

《深入细品浏览器原理「流程图」》

《JavaScript 已进入第三个时代,未来将何去何从?》

《前端上传前预览文件 image、text、json、video、audio「实践」》

《深入细品 EventLoop 和浏览器渲染、帧动画、空闲回调的关系》

《推荐13个有用的JavaScript数组技巧「值得收藏」》

《前端必备基础知识:window.location 详解》

《不要再依赖CommonJS了》

《犀牛书作者:最该忘记的JavaScript特性》

《36个工作中常用的JavaScript函数片段「值得收藏」》

《Node + H5 实现大文件分片上传、断点续传》

《一文了解文件上传全过程(1.8w字深度解析)「前端进阶必备」》

《【实践总结】关于小程序挣脱枷锁实现批量上传》

《手把手教你前端的各种文件上传攻略和大文件断点续传》

《字节跳动面试官:请你实现一个大文件上传和断点续传》

《谈谈前端关于文件上传下载那些事【实践】》

《手把手教你如何编写一个前端图片压缩、方向纠正、预览、上传插件》

《最全的 JavaScript 模块化方案和工具》

《「前端进阶」JS中的内存管理》

《JavaScript正则深入以及10个非常有意思的正则实战》

《前端面试者经常忽视的一道JavaScript 面试题》

《一行JS代码实现一个简单的模板字符串替换「实践」》

《JS代码是如何被压缩的「前端高级进阶」》

《前端开发规范:命名规范、html规范、css规范、js规范》

《【规范篇】前端团队代码规范最佳实践》

《100个原生JavaScript代码片段知识点详细汇总【实践】》

《关于前端174道 JavaScript知识点汇总(一)》

《关于前端174道 JavaScript知识点汇总(二)》

《关于前端174道 JavaScript知识点汇总(三)》

《几个非常有意思的javascript知识点总结【实践】》

《都2020年了,你还不会JavaScript 装饰器?》

《JavaScript实现图片合成下载》

《70个JavaScript知识点详细总结(上)【实践】》

《70个JavaScript知识点详细总结(下)【实践】》

《开源了一个 JavaScript 版敏感词过滤库》

《送你 43 道 JavaScript 面试题》

《3个很棒的小众JavaScript库,你值得拥有》

《手把手教你深入巩固JavaScript知识体系【思维导图】》

《推荐7个很棒的JavaScript产品步骤引导库》

《Echa哥教你彻底弄懂 JavaScript 执行机制》

《一个合格的中级前端工程师需要掌握的 28 个 JavaScript 技巧》

《深入解析高频项目中运用到的知识点汇总【JS篇】》

《JavaScript 工具函数大全【新】》

《从JavaScript中看设计模式(总结)》

《身份证号码的正则表达式及验证详解(JavaScript,Regex)》

《浏览器中实现JavaScript计时器的4种创新方式》

《Three.js 动效方案》

《手把手教你常用的59个JS类方法》

《127个常用的JS代码片段,每段代码花30秒就能看懂-【上】》

《深入浅出讲解 js 深拷贝 vs 浅拷贝》

《手把手教你JS开发H5游戏【消灭星星】》

《深入浅出讲解JS中this/apply/call/bind巧妙用法【实践】》

《手把手教你全方位解读JS中this真正含义【实践】》

《书到用时方恨少,一大波JS开发工具函数来了》

《干货满满!如何优雅简洁地实现时钟翻牌器(支持JS/Vue/React)》

《手把手教你JS 异步编程六种方案【实践】》

《让你减少加班的15条高效JS技巧知识点汇总【实践】》

《手把手教你JS开发H5游戏【黄金矿工】》

《手把手教你JS实现监控浏览器上下左右滚动》

《JS 经典实例知识点整理汇总【实践】》

《2.6万字JS干货分享,带你领略前端魅力【基础篇】》

《2.6万字JS干货分享,带你领略前端魅力【实践篇】》

《简单几步让你的 JS 写得更漂亮》

《恭喜你获得治疗JS this的详细药方》

《谈谈前端关于文件上传下载那些事【实践】》

《面试中教你绕过关于 JavaScript 作用域的 5 个坑》

《Jquery插件(常用的插件库)》

《【JS】如何防止重复发送ajax请求》

《JavaScript+Canvas实现自定义画板》

《Continuation 在 JS 中的应用「前端篇」》

作者:AlienZHOU

转发链接:https://zhuanlan.zhihu.com/p/149351900

你可能感兴趣的:(phalapi可以依赖注入么)