IDEA外掛開發——React專案外掛
設計部分
因為平時在做專案的時候,總是會有一些重複程式碼的工作量,作為一個有追求的程式設計師,當然不會讓自己一直重複這些勞動。於是,就有了IDEA外掛開發這個方案了。IDEA外掛開發的資料非常少,大部分都要閱讀IDEA的原始碼來探索。
首先明確我的目標:根據模組和頁面名稱,自動初始化一系列的頁面,其中的變數、類名、檔名等均根據模組名稱和頁面名稱生成。
第二步就是設計互動,我初步計劃是,模組和頁面的資料夾由自己手工建立,在頁面資料夾上右鍵,點選生成,生成資料夾裡面的內容。
建立專案
新建一個IntelliJ Platform Plugin
,寫好專案名稱,進入開發介面。
首先稍微介紹下目錄結構。
1. resources
META-INF
中防止了外掛的配置,以後模板檔案也會放置在resource
目錄。 2.
src
中放置程式碼。 3. 其他如
.idea
和out
等與編碼無關,是IDE配置和編譯輸出檔案等。
瞭解了目錄結構,就要開始實現第一個目標。
實現選單項
在IDEA中,每個動作都是一個action,所以第一步需要建立一個action。
在我的版本的IDEA中,建立的目錄在這個位置,不同版本的IDEA位置可能不同,但是都大同小異,應該都叫做Action。
其中要填幾個欄位:
1. Action ID,也就是id了,保證唯一就可以了,隨便怎麼填,比如: My.newPage
2. Class Name,會為你建立的類的名稱,如NewPageAction。
3. Name,展示的名稱,如New Page Init。
4. Description,描述,滑鼠移動到選單上的時候,底部欄對此選單的描述,隨便寫一下就可以了。
5. Add To Group,這是最重要的一欄,首先選Group,這是代表選項出現在哪一類選單中,由於我是要讓我的選單出現在專案資料夾的右鍵選單中,所以選擇的是ProjectViewPopupMenu,旁邊的Actions欄作用是和Anchor一起的,表示選項出現在現有選項的前面還是後面。我的選項出現在最後就可以了,所以不用選擇Actions,直接在Anchor欄選擇Last。
6. Keyboard Shortcuts,快捷鍵,我不需要,所以不設定了。
在建立完之後,會自動在META-INF
的plugin.xml
中,建立一段標籤:
<action id="My.newPage" class="com.my.plugin.action.NewPageAction" text="New Page Init" description="New Page Init">
<add-to-group group-id="ProjectViewPopupMenu" anchor="last"/>
</action>
並且會在src目錄我們指定的包名中建立一個繼承自AnAction的類,其中實現了actionPerformed方法,這個方法代表選項被點選的時候觸發的方法。
獲取需要的資料
首先介紹下我的外掛,我的專案結構如下:
模組資料夾 ->
頁面資料夾 ->
action資料夾
action檔案
actionType檔案
reducer資料夾
reducer檔案
component資料夾
元件檔案
主頁面檔案
所以我的外掛目標是,建立好模組資料夾和頁面資料夾之後,在頁面資料夾上點選右鍵,選擇New Page Init,就可以生成下面的所有資料夾和檔案。
那麼第一步,就是需要獲取到資料夾的名稱了。
final DataContext dataContext = e.getDataContext(); //通過事件獲取到當前的上下文
final IdeView view = LangDataKeys.IDE_VIEW.getData(dataContext); //獲取到當前IDE的物件
if (view == null) {
return;
}
final PsiDirectory dir = view.getOrChooseDirectory(); // 拿到當前選擇的資料夾,也就是右鍵點選的資料夾
final Project project = PlatformDataKeys.PROJECT.getData(dataContext); // 拿到專案物件
if (dir == null || project == null) return;
到此,我們就拿到了需要的最基本的東西,準備工作做好了,因為頁面中可能會用到各種各樣的名稱形式,所以現在來對基礎資料做一些處理:
String[] split = dir.getName().split("-"); // 因為專案的約定,統一使用-來間隔單詞
String dashName = dir.getName(); // 帶-的名稱,如my-page
String underscoreName = StringUtil.join(split, "_"); // 帶下劃線的名稱,如my_page
String firstUpperCamelCaseName = Arrays.stream(split).map((w) -> Character.toUpperCase(w.charAt(0)) + w.substring(1).toLowerCase()).collect(Collectors.joining()); //所有首字母都大寫的駝峰名稱,如MyPage
String allUpperCaseUnderscoreName = underscoreName.toUpperCase(); // 帶下劃線的全大寫名稱,如MY_PAGE
String moduleName = "error"; // 模組名稱,稍後處理
String moduleNameCamelCase = "error"; // 駝峰模組名稱,稍後處理
String moduleUnderscoreName = "error"; // 模組下劃線名稱,稍後處理
PsiDirectory dirParent = dir.getParent(); // 獲取page資料夾的上一層,也就是模組資料夾
if (dirParent != null) {
moduleName = dirParent.getName(); // 模組名稱
if (dirParent.getName().contains("-")) {
String[] parts = dirParent.getName().split("-");
StringBuilder camelCaseString = new StringBuilder();
for (int i = 0; i < parts.length; i++) {
String part = parts[i];
if (i != 0) {
camelCaseString.append(part.substring(0, 1).toUpperCase()).append(part.substring(1).toLowerCase());
} else {
camelCaseString.append(part);
}
}
moduleNameCamelCase = camelCaseString.toString(); // 駝峰模組名
} else {
moduleNameCamelCase = dirParent.getName();
}
moduleUnderscoreName = StringUtil.join(moduleName.split("-"), "_"); // 下劃線模組名
}
基礎資料就準備好了,可以進入下一步了。
頁面模板
IDEA中,因為內建了Velocity,所以允許我們定義一些模板,首先先在resources
中,建立一個資料夾,用於存放模板檔案,名稱叫做fileTemplates.internal
,注意必須使用這個名稱。
在下面新建所需要的模板檔案,帶上拓展名,並且以.ft結尾,如actions.tsx.ft
。
檔案的語法與Velocity相同,把最基礎的程式碼複製過來之後,將要替換的部分,使用Velocity的模板語法替換,就完成模板的編寫了。
編寫好了模板,還需要在plugin.xml中進行一些配置,讓系統能夠讀取到相應的模板。
找到extensions
標籤,在標籤下加入如下標籤:
<internalFileTemplate name="actions"/>
其中name為不帶檔案拓展名的名稱,如actions.tsx.ft,就填入actions。這樣在以後使用的時候,IDEA就會將模板名稱與檔案對應起來。
模板編寫好了之後,回到原來的Action程式碼中,現在要進行的步驟是獲取模板檔案,並將值傳入模板檔案中,需要用到FileTemplateManager這個類了。
FileTemplateManager.getInstance(project).getInternalTemplate("actions"); // 獲取actions模板
在拿到模板物件之後,還需要傳入之前準備好的一些值:
Properties properties = new Properties();
properties.put("dashName", dashName);
properties.put("moduleName", moduleName);
properties.put("moduleNameCamelCase", moduleNameCamelCase);
properties.put("UpperPageName", firstUpperCamelCaseName);
properties.put("underscore_name", underscoreName);
properties.put("pageTitle", "請定義標題");
properties.put("allUpperCaseUnderscoreName", allUpperCaseUnderscoreName);
現在,就可以開始傳入值了:
PsiDirectory actionsDirectory = dir.createSubdirectory("actions"); // 先在page目錄下,建立一個目錄actions
FileTemplate actionTemplate = FileTemplateManager.getInstance(project).getInternalTemplate("actions"); // 獲取到actions的模板
try {
FileTemplateUtil.createFromTemplate(actionTemplate, underscoreName + ".actions.tsx", properties, actionsDirectory); // 使用FileTemplateUtil的工具方法,在對應目錄下,生成檔案
} catch (Exception e1) {
e1.printStackTrace();
}
到此,好像一個完整的編碼流程就完成,那麼執行一下吧。你會發現,執行的時候報錯了。因為IDEA將寫入有關的操作,都要放到另外的一個執行緒中執行,所以,需要告訴IDEA要執行的操作是一個寫操作。在程式碼外面包上一層:
ApplicationManager.getApplication().runWriteAction(() -> {
// .......我們的程式碼
});
這樣,IDEA就會在寫入操作的執行緒中執行我們的程式碼了。
測試並生成外掛
開始執行的時候,需要配置一個執行環境,也就是啟動另外一個IDEA,你可以去網上下載社群版本的IDEA來進行測試。如果以後需要編寫到特殊的IDEA外掛,比如TypeScript相關或者框架相關的外掛,那麼也可以配置付費版的IDEA或者Webstorm之類的來做測試。
在執行之前,最好還要先指定一下IDEA的最低版本,在plugin.xml中,有一段被註釋的程式碼:
<!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html for description -->
<!-- <idea-version since-build="141.0"/> --> 《------就是它
開啟這個註釋,就代表IDEA會支援這個版本的api,具體版本查詢,可以在上面的那行註釋連結中去找。
到此應該能夠將程式碼跑起來了,並且能夠預覽到外掛的效果了,如果還有小bug,IDEA的除錯是很方便的。接下來就是生成正式的外掛了。
生成外掛只需要點選選單上Build -> Prepare Plugin Module ‘xxx’ For Deployment,就可以生成出對應的jar包了。
結語
有些同學可能說,這些功能不寫IDEA的plugin也能寫啊。確實,功能怎麼樣都能實現,只是看怎麼樣的實現最優美。在IDEA中點選右鍵,選擇生成頁面,這就是我覺得最優美的實現,所以才這麼折騰在IDEA中實現這個功能。
並且,這個功能不是這個外掛所有的功能,後續要做的功能,應該只有在IDEA中實現才最輕鬆,比如:解析reducer樹並展示在IDE中;生成頁面的時候可選頁面初始化react元件(類似android)等等。這些功能都會依賴到js的語法解析,在IDEA中,psi直接可以訪問到解析好的語法資料,後續我應該也會出更高階的IDEA外掛教學。