Maison > outils de développement > VSCode > le corps du texte

vscode+babel développe un plug-in qui supprime intelligemment les variables inutilisées (combat réel)

青灯夜游
Libérer: 2021-12-24 18:28:43
avant
3314 Les gens l'ont consulté

Cet article partage une méthode pour développer une suppression intelligente des plug-ins variables inutilisés dans vscode combinée avec babel. J'espère que cela sera utile à tout le monde !

vscode+babel développe un plug-in qui supprime intelligemment les variables inutilisées (combat réel)

vscode est devenu l'un des outils de développement indispensables pour le front-end. La raison pour laquelle vscode est privilégié par les développeurs, je pense, a beaucoup à voir avec son système de plug-in "omnipotent". Au travail, nous pouvons l'utiliser pour développer des plug-ins de type outil purs, et nous pouvons également l'utiliser pour développer des plug-ins fonctionnels qui sont combinés avec les activités de l'entreprise. >. Ici, je partage un plug-in qui peut intelligemment supprimer les variables inutilisées en combinant babel. J'espère qu'il inspirera et aidera tout le monde à développer des plug-ins vscode. [Apprentissage recommandé : "tutoriel d'introduction au vscode工具型的插件,也可以用它开发一些和公司业务相结合的功能插件。在这里我分享一个通过结合babel来实现一个能够智能移除未使用的变量插件,希望对大家开发 vscode 插件有一定的启发和帮助。【推荐学习:《vscode入门教程》】

正文

今天我们首先来熟悉一下 vscode 插件项目的搭建流程

1、使用官方提供的脚手架初始化一个项目

安装脚手架

# npm 形式
npm install -g yo generator-code
# yarn 形式
yarn global add yo generator-code
Copier après la connexion

运行脚手架

# 运行脚手架
yo code
Copier après la connexion

选择模板,考虑到有些开发者对 TypeScript 并不熟悉,所以我们这里选择 New Extension (JavaScript)

? What type of extension do you want to create? New Extension (JavaScript)
? What's the name of your extension? rm-unuse-var
? What's the identifier of your extension? rm-unuse-var
? What's the description of your extension? 移除未使用的变量
? Enable JavaScript type checking in 'jsconfig.json'? Yes
? Initialize a git repository? Yes
? Which package manager to use? yarn
Copier après la connexion

这是我们最终生成的目录结构

vscode+babel développe un plug-in qui supprime intelligemment les variables inutilisées (combat réel)

我们先来运行一下这个插件试试

vscode+babel développe un plug-in qui supprime intelligemment les variables inutilisées (combat réel)

点击上面运行按钮,会打开一个新的 vscode 窗口,在新窗口中按下Ctrl+Shift+P输入Hello World,在窗口右下角会看到一个提示框,说明我们第一个 vscode 插件运行成功运行了。

vscode+babel développe un plug-in qui supprime intelligemment les variables inutilisées (combat réel)

2、自定义命令、快捷键、菜单

vscode 插件很多功能都是基于一个个命令实现的,我们可以自定义一些命令,这个命令将出现在按下Ctrl+Shift+P后的命令列表里面,同时可以给命令配置快捷键、配置资源管理器菜单、编辑器菜单、标题菜单、下拉菜单、右上角图标等。

3、如何添加命令列表

package.json 部分配置

{
  // 扩展的激活事件
  "activationEvents": ["onCommand:rm-unuse-var.helloWorld"],
  // 入口文件
  "main": "./extension.js",
  // 添加指令
  "contributes": {
    "commands": [
      {
        // 这里的值必须和activationEvents里面配置的一样
        "command": "rm-unuse-var.helloWorld",
        // 这个就是我们指令的名称,可以修改这里的值重新运行插件试试看
        "title": "Hello World"
      }
    ]
  }
}
Copier après la connexion

在开发中快捷键的使用方式是最便捷的,接下来我们修改一下配置,让插件支持快捷键的方式运行。

{
  "contributes": {
    "commands": [
      {
        // 这里的值必须和activationEvents里面配置的一样
        "command": "rm-unuse-var.helloWorld",
        // 这个就是我们指令的名称,可以修改这里的值重新运行插件试试看
        "title": "Hello World"
      }
    ],
    // 快捷键绑定
    "keybindings": [
      {
        "command": "rm-unuse-var.helloWorld",
        "key": "ctrl+6",
        "mac": "cmd+6"
      }
    ]
  }
}
Copier après la connexion

我们再重新运行一下,通过快捷键Ctrl+6看看我们的插件是否能够正常运行。没错就是这么简单,我们的插件已经能够支持快捷键的形式运行了。

4、叫 helloWorld 太土了,下一步我们来修改一下指令的名称

package.json

{
  "activationEvents": ["onCommand:rm-unuse-var.rm-js-var"],
  "main": "./extension.js",
  "contributes": {
    "commands": [
      {
        "command": "rm-unuse-var.rm-js-var",
        "title": "Hello World"
      }
    ],
    "keybindings": [
      {
        "command": "rm-unuse-var.rm-js-var",
        "key": "ctrl+6",
        "mac": "cmd+6"
      }
    ]
  }
}
Copier après la connexion

因为我们在extension.js中注册了指令的名称,所以也要同步修改

let disposable = vscode.commands.registerCommand(
  "rm-unuse-var.rm-js-var",
  function () {
    vscode.window.showInformationMessage("Hello World from rm-unuse-var!");
  }
);
Copier après la connexion

5、安装babel相关库

我们修改代码可以分为 3 个步骤

1、将代码解析成 AST 语法树 2、遍历修改 AST 语法树 3、根据修改过的 AST 语法树生成新的代码

这 3 个步骤 babel 都有对应的库来处理

  • @babel/parser生成 AST 语法树,文档地址(https://www.babeljs.cn/docs/babel-parser)

  • @babel/traverse遍历 AST 语法树,文档地址(https://www.babeljs.cn/docs/babel-traverse)

  • @babel/generator根据 AST 语法树生成代码,文档地址(https://www.babeljs.cn/docs/babel-generator)

  • @babel/types"]

    Texte

  • Aujourd'hui, nous allons d'abord nous familiariser avec le processus de construction du projet de plug-in vscode

1 Initialiser un projet en utilisant. l'échafaudage officiel

Installez l'échafaudage

const say = () => {
  console.log("hello");
};
Copier après la connexion

Exécutez l'échafaudage

function say() {
  console.log("hello");
}
Copier après la connexion

Choisissez un modèle Considérant que certains développeurs ne sont pas familiers avec TypeScript, nous choisissons Nouvelle extension (JavaScript)

const t = require("@babel/types");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generate = require("@babel/generator").default;
// 1、将代码解析成 AST 语法树
const ast = parser.parse(`const say = () => {
  console.log("hello");
};`);
// 2、遍历修改 AST 语法树
traverse(ast, {
  VariableDeclaration(path) {
    const { node } = path;
    // 写死找到第一个申明
    const declaration = node.declarations[0];
    // 定义的内容
    const init = declaration.init;
    // 判断是否是箭头函数
    if (t.isArrowFunctionExpression(init)) {
      // 将原来的表达式替换成新生成的函数
      path.replaceWith(
        t.functionDeclaration(
          declaration.id,
          init.params,
          init.body,
          init.generator,
          init.async
        )
      );
    }
  },
});
// 3、根据修改过的 AST 语法树生成新的代码
console.log(generate(ast).code);
/*
function say() {
  console.log("hello");
}
*/
Copier après la connexion

Ceci. est notre structure de répertoire générée finale

vscode+babel développe un plug-in qui supprime intelligemment les variables inutilisées (combat réel)

Essayons d'abord d'exécuter ce plug-in🎜🎜vscode+babel développe un plug-in qui supprime intelligemment les variables inutilisées (combat réel)🎜🎜Cliquez sur le bouton Exécuter ci-dessus et une nouvelle fenêtre vscode s'ouvrira. Dans la nouvelle fenêtre, appuyez sur Ctrl+Shift+P et entrez Hello World </code >, vous verrez une boîte de dialogue dans le coin inférieur droit de la fenêtre, indiquant que notre premier plug-in vscode a été exécuté avec succès. 🎜🎜<img src="https://img.php.cn/upload/image/274/863/510/164017326230798vscode+babel développe un plug-in qui supprime intelligemment les variables inutilisées (combat réel)" title="164017326230798vscode+babel développe un plug-in qui supprime intelligemment les variables inutilisées (combat réel)" alt="vscode+babel développe un plug-in qui supprime intelligemment les variables inutilisées (combat réel)"/>🎜<h3 data-id="heading-3">🎜2. Commandes personnalisées, touches de raccourci, menus🎜🎜🎜De nombreuses fonctions du plug-in vscode sont implémentées en fonction de commandes. Nous pouvons personnaliser certaines commandes, qui apparaîtront en appuyant sur Dans le. liste de commandes après avoir cliqué sur <code>Ctrl+Shift+P, vous pouvez également configurer les touches de raccourci pour les commandes, configurer le menu du gestionnaire de ressources, le menu de l'éditeur, le menu titre, le menu déroulant, l'icône du coin supérieur droit, etc. 🎜

🎜3. Comment ajouter une liste de commandes🎜🎜🎜configuration partielle de package.json🎜
import * as vscode from "vscode";
// 当前打开的文件
const { activeTextEditor } = vscode.window;
// 然后通过document下的getText就能轻松获取到我们的代码了
const code = activeTextEditor.document.getText();
Copier après la connexion
Copier après la connexion
🎜L'utilisation de touches de raccourci est le moyen le plus pratique en développement. Ensuite, modifions. la configuration, laissez le plug-in prendre en charge l'exécution par des touches de raccourci. 🎜
activeTextEditor.edit((editBuilder) => {
  editBuilder.replace(
    // 因为我们要全文件替换,所以我们需要定义一个从头到位的区间
    new vscode.Range(
      new vscode.Position(0, 0),
      new vscode.Position(activeTextEditor.document.lineCount + 1, 0)
    ),
    // 我们的新代码
    generate(ast).code
  );
});
Copier après la connexion
Copier après la connexion
🎜 Exécutons-le à nouveau et utilisons la touche de raccourci Ctrl+6 pour voir si notre plug-in peut fonctionner normalement. Oui, c'est aussi simple que cela. Notre plug-in peut déjà prendre en charge l'exécution en tant que touches de raccourci. 🎜

🎜4. Appeler helloWorld est trop ringard. Ensuite, changeons le nom de la commande 🎜🎜🎜package.json🎜
import vue from "vue";
const a = { test1: 1, test2: 2 };
const { test1, test2 } = a;
function b() {}
let c = () => {};
var d = () => {};
Copier après la connexion
Copier après la connexion
🎜car nous sommes dans extension.js. < Le nom de la commande est enregistré dans /code>, il doit donc être modifié simultanément🎜<div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">var a = 1; var b = 2; console.log(a);</pre><div class="contentsignin">Copier après la connexion</div></div><div class="contentsignin">Copier après la connexion</div></div><h3 data-id="heading-6">🎜5 Installez les bibliothèques associées à <code>babel🎜🎜🎜. Nous pouvons modifier le code divisé en 3 étapes🎜🎜1 Analyser le code dans un arbre de syntaxe AST. 2. Parcourez et modifiez l'arborescence syntaxique AST 3. Générez un nouveau code basé sur l'arbre de syntaxe AST modifié🎜🎜Babel a des bibliothèques correspondantes pour gérer ces trois étapes🎜
  • 🎜 @babel/parser Générer l'arbre de syntaxe AST, adresse du document (https://www.babeljs.cn/docs/babel-parser)🎜🎜
  • 🎜@babel/traverse parcourir l'arbre de syntaxe AST, adresse du document (https://www.babeljs.cn/docs/babel-traverse) 🎜🎜
  • 🎜@babel/generatorGénérer du code basé sur l'arbre de syntaxe AST, adresse du document ( https:/ /www.babeljs.cn/docs/babel-generator) 🎜🎜
  • 🎜@babel/types bibliothèque d'outils, adresse du document (https://www.babeljs.cn/docs /babel- types) 🎜🎜🎜🎜🎜6. Jetons un coup d'œil à l'utilisation de base de ces bibliothèques, comme l'implémentation d'une implémentation de code qui convertit la fonction flèche es6 en une fonction normale 🎜🎜🎜Avant la conversion🎜
    var a = 1;
    console.log(a);
    Copier après la connexion
    Copier après la connexion
    🎜Après la conversion🎜
    const t = require("@babel/types");
    const parser = require("@babel/parser");
    const traverse = require("@babel/traverse").default;
    const generate = require("@babel/generator").default;
    
    const ast = parser.parse(`var a = 1;
    var b = 2;
    console.log(a);`);
    
    traverse(ast, {
      VariableDeclaration(path) {
        const { node } = path;
        const { declarations } = node;
        // 此处便利可以处理 const a = 1,b = 2; 这种场景
        node.declarations = declarations.filter((declaration) => {
          const { id } = declaration;
          // const { b, c } = a;
          if (t.isObjectPattern(id)) {
            // path.scope.getBinding(name).referenced 判断变量是否被引用
            // 通过filter移除掉没有使用的变量
            id.properties = id.properties.filter((property) => {
              const binding = path.scope.getBinding(property.key.name);
              return !!binding?.referenced;
            });
            // 如果对象中所有变量都没有被应用,则该对象整个移除
            return id.properties.length > 0;
          } else {
            // const a = 1;
            const binding = path.scope.getBinding(id.name);
            return !!binding?.referenced;
          }
        });
        // 如果整个定义语句都没有被引用则整个移除
        if (node.declarations.length === 0) {
          path.remove();
        }
      },
    });
    console.log(generate(ast).code);
    Copier après la connexion
    Copier après la connexion
    🎜 La partie code est codée en dur à titre de référence d'apprentissage uniquement🎜
    const t = require("@babel/types");
    const parser = require("@babel/parser");
    const traverse = require("@babel/traverse").default;
    const generate = require("@babel/generator").default;
    
    const ast = parser.parse(
      `import vue from &#39;vue&#39;;
      var a = 1;
    var b = 2;
    var { test1, test2 } = { test1: 1, test2: 2 };
    function c(){}
    function d(){}
    d();
    console.log(a, test1);`,
      {
        sourceType: "module",
      }
    );
    
    traverse(ast, {
      // 处理 const var let
      VariableDeclaration(path) {
        const { node } = path;
        const { declarations } = node;
    
        node.declarations = declarations.filter((declaration) => {
          const { id } = declaration;
          if (t.isObjectPattern(id)) {
            id.properties = id.properties.filter((property) => {
              const binding = path.scope.getBinding(property.key.name);
              return !!binding?.referenced;
            });
            return id.properties.length > 0;
          } else {
            const binding = path.scope.getBinding(id.name);
            return !!binding?.referenced;
          }
        });
    
        if (node.declarations.length === 0) {
          path.remove();
        }
      },
      // 处理 import
      ImportDeclaration(path) {
        const { node } = path;
        const { specifiers } = node;
        if (!specifiers.length) {
          return;
        }
        node.specifiers = specifiers.filter((specifier) => {
          const { local } = specifier;
          const binding = path.scope.getBinding(local.name);
          return !!binding?.referenced;
        });
        if (node.specifiers.length === 0) {
          path.remove();
        }
      },
      // 处理 function
      FunctionDeclaration(path) {
        const { node } = path;
        const { id } = node;
        const binding = path.scope.getBinding(id.name);
        if (!binding?.referenced) {
          path.remove();
        }
      },
    });
    console.log(generate(ast).code);
    Copier après la connexion
    Copier après la connexion
    🎜De nombreux étudiants doivent être curieux que notre expression soit relativement simple maintenant si elle est complexe, l'imbrication de définitions sera très profonde et compliquée. Comment devrions-nous savoir quel nœud remplacer. à ce moment ? . En fait, vous pouvez utiliser 🎜astexplorer.net/🎜 Il s'agit d'un site Web permettant de convertir l'AST en ligne. Nous pouvons ouvrir deux fenêtres, mettre le code avant conversion dans la première fenêtre et mettre l'interface qui doit être convertie dans la deuxième fenêtre. A ce stade, nous pouvons comparer les différences avant et après la conversion pour implémenter notre code. 🎜

    vscode+babel développe un plug-in qui supprime intelligemment les variables inutilisées (combat réel)

    vscode+babel développe un plug-in qui supprime intelligemment les variables inutilisées (combat réel)

    6、思考插件如何实现?

    1、获取编辑器当前打开的 js 文件的代码 2、将代码解析成 AST 语法树 3、遍历 AST 语法树,删除未使用的定义 4、根据修改过的 AST 语法树生成新的代码 5、替换当前 js 文件的代码

    其中 2、4 我们已经会了,接下来只需要看下 1、3、5 如何实现就行

    1 和 5 我们可以通过 vscode 提供的方法

    1、获取编辑器当前打开的 js 文件的代码

    import * as vscode from "vscode";
    // 当前打开的文件
    const { activeTextEditor } = vscode.window;
    // 然后通过document下的getText就能轻松获取到我们的代码了
    const code = activeTextEditor.document.getText();
    Copier après la connexion
    Copier après la connexion

    5、替换当前 js 文件的代码

    activeTextEditor.edit((editBuilder) => {
      editBuilder.replace(
        // 因为我们要全文件替换,所以我们需要定义一个从头到位的区间
        new vscode.Range(
          new vscode.Position(0, 0),
          new vscode.Position(activeTextEditor.document.lineCount + 1, 0)
        ),
        // 我们的新代码
        generate(ast).code
      );
    });
    Copier après la connexion
    Copier après la connexion

    好了接下来我们就剩核心的第 3 步了。

    3、遍历 AST 语法树,删除未使用的定义

    我们先来分析一下,未使用的定义包含了哪些?

    import vue from "vue";
    const a = { test1: 1, test2: 2 };
    const { test1, test2 } = a;
    function b() {}
    let c = () => {};
    var d = () => {};
    Copier après la connexion
    Copier après la connexion

    然后在线 ast 转换网站,复制这些内容进去看看生成的语法树结构

    vscode+babel développe un plug-in qui supprime intelligemment les variables inutilisées (combat réel)

    我们先来实现一个例子吧,比如把下面代码中没有用的变量移除掉

    转换前

    var a = 1;
    var b = 2;
    console.log(a);
    Copier après la connexion
    Copier après la connexion

    转换后

    var a = 1;
    console.log(a);
    Copier après la connexion
    Copier après la connexion
    • scope.getBinding(name) 获取当前所有绑定
    • scope.getBinding(name).referenced 绑定是否被引用
    • scope.getBinding(name).constantViolations 获取当前所有绑定修改
    • scope.getBinding(name).referencePaths 获取当前所有绑定路径

    代码实现

    const t = require("@babel/types");
    const parser = require("@babel/parser");
    const traverse = require("@babel/traverse").default;
    const generate = require("@babel/generator").default;
    
    const ast = parser.parse(`var a = 1;
    var b = 2;
    console.log(a);`);
    
    traverse(ast, {
      VariableDeclaration(path) {
        const { node } = path;
        const { declarations } = node;
        // 此处便利可以处理 const a = 1,b = 2; 这种场景
        node.declarations = declarations.filter((declaration) => {
          const { id } = declaration;
          // const { b, c } = a;
          if (t.isObjectPattern(id)) {
            // path.scope.getBinding(name).referenced 判断变量是否被引用
            // 通过filter移除掉没有使用的变量
            id.properties = id.properties.filter((property) => {
              const binding = path.scope.getBinding(property.key.name);
              return !!binding?.referenced;
            });
            // 如果对象中所有变量都没有被应用,则该对象整个移除
            return id.properties.length > 0;
          } else {
            // const a = 1;
            const binding = path.scope.getBinding(id.name);
            return !!binding?.referenced;
          }
        });
        // 如果整个定义语句都没有被引用则整个移除
        if (node.declarations.length === 0) {
          path.remove();
        }
      },
    });
    console.log(generate(ast).code);
    Copier après la connexion
    Copier après la connexion

    7、了解基本处理流程之后,我们就来看下最终的代码实现吧

    const t = require("@babel/types");
    const parser = require("@babel/parser");
    const traverse = require("@babel/traverse").default;
    const generate = require("@babel/generator").default;
    
    const ast = parser.parse(
      `import vue from &#39;vue&#39;;
      var a = 1;
    var b = 2;
    var { test1, test2 } = { test1: 1, test2: 2 };
    function c(){}
    function d(){}
    d();
    console.log(a, test1);`,
      {
        sourceType: "module",
      }
    );
    
    traverse(ast, {
      // 处理 const var let
      VariableDeclaration(path) {
        const { node } = path;
        const { declarations } = node;
    
        node.declarations = declarations.filter((declaration) => {
          const { id } = declaration;
          if (t.isObjectPattern(id)) {
            id.properties = id.properties.filter((property) => {
              const binding = path.scope.getBinding(property.key.name);
              return !!binding?.referenced;
            });
            return id.properties.length > 0;
          } else {
            const binding = path.scope.getBinding(id.name);
            return !!binding?.referenced;
          }
        });
    
        if (node.declarations.length === 0) {
          path.remove();
        }
      },
      // 处理 import
      ImportDeclaration(path) {
        const { node } = path;
        const { specifiers } = node;
        if (!specifiers.length) {
          return;
        }
        node.specifiers = specifiers.filter((specifier) => {
          const { local } = specifier;
          const binding = path.scope.getBinding(local.name);
          return !!binding?.referenced;
        });
        if (node.specifiers.length === 0) {
          path.remove();
        }
      },
      // 处理 function
      FunctionDeclaration(path) {
        const { node } = path;
        const { id } = node;
        const binding = path.scope.getBinding(id.name);
        if (!binding?.referenced) {
          path.remove();
        }
      },
    });
    console.log(generate(ast).code);
    Copier après la connexion
    Copier après la connexion

    8、vscode 设置我们的插件只支持 js 文件的限制

    因为我们现在实现是针对 js 文件的,所以打开其他类型的文件我们可以让我们的快捷键失效。 我们可以修改package.jsonpackage.json

    {
      "contributes": {
        "commands": [
          {
            "command": "rm-unuse-var.remove",
            "title": "Hello World"
          }
        ],
        "keybindings": [
          {
            "command": "rm-unuse-var.remove",
            "key": "ctrl+6",
            "mac": "cmd+6",
            "when": "resourceLangId == javascript"
          }
        ]
      }
    }
    Copier après la connexion

    9、整合到我们前面创建的项目中去

    此处省略... 相信看了上面这些介绍大家已经完全有能力自己整合了

    10、打包发布插件

    打包我们可以vsce工具

    全局安装 vsce

    # npm
    npm i vsce -g
    # yarn
    yarn global add vsce
    Copier après la connexion

    打包插件

    打包前先修改 README.md 文件否则会报错

    vsce package
    Copier après la connexion

    执行完毕之后会生成一个.vsix 文件

    如果要在本地 vscode 使用可以直接导入

    vscode+babel développe un plug-in qui supprime intelligemment les variables inutilisées (combat réel)

    如果要发布到市场的话,我们需要先注册账号 https://code.visualstudio.com/api/working-with-extensions/publishing-extension#publishing-extensions

    # 登录账号
    vsce login your-publisher-name
    # 发布
    vsce publish
    Copier après la connexion

    发布成功之后就能在我们的市场上看到了 marketplace.visualstudio.com/items?itemN… 也可以在 vscode 中搜索打我们的插件

    vscode+babel développe un plug-in qui supprime intelligemment les variables inutilisées (combat réel)

    总结

    到此为止,相信大家对 vscode 插件开发的基本流程已经有了了解。

    觉得文章对你有所帮助,可以点个赞 

    当然 vscode 插件还有非常多的配置没有介绍,后面如果有时间可以单独整理成一篇文章来介绍

    如果在开发过程中有问题或者其他前端技术问题也可以加我微信rjjs1221交流,或者直接在评论区回复。

    源码地址 https://github.com/taoxhsmile/rm-unuse-var

    更多关于VSCode的相关知识,请访问:vscode教程!!

    Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:juejin.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!