21
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

領域展開「Gradle」で Node.js を制する

Last updated at Posted at 2021-12-02

失礼しました...

はじめに

オークファンの大きめのデータを担当するバックエンドシステムでは Kotlin + Spring Boot + Gradle の構成がよく採用されています。少し前まではこの構成で作成されるのは、バッチプログラムや Web API がほとんどでしたが、最近さらに Vue.js を追加して UI を持った Single Page Application (SPA) の Web アプリも作成するようになってきました。

Spring Boot のプロジェクトを Spring Initializr で作成すると、Gradle Wrapper が付属しています。これにより、開発環境では JDK のみ用意するだけでプロジェクトのテストやビルドなどをお手軽に実行できていました。

ところが、JavaScript フレームワークである Vue.js がプロジェクトに追加されたことにより、別途 Node.js 環境を用意しなければいけなくなってしまい、これが結構面倒なうえに、プロジェクトメンバーに開発環境を構築してもらう際にトラブルが発生することが少なくありませんでした。

このような場合には Docker にご登場いただくのが一般的ですが、ここではあえて Docker を使わない縛りを課し、すでにプロジェクトに入っている Gradle での問題解決方法を開示していこうと思います。

個人的には Maven や Gradle (Ant は〇ソ...) 支配下の Java や Kotlin でこれまで戦うことが多かったので、npm (yarn は使いやすいですね...) の支配する不慣れな Node.js に対して有利に戦えるといいなと思ったのが発端でした。

プロジェクトの前提は以下としました。

  • 開発環境に必要なのは JDK のみ
  • Node.js まわりで必要なものは Gradle Plugin for Node で調達
  • 幅広い OS (ここでは macOS、Linux、Windows を想定) で開発可能

今回のサンプルプロジェクトの構築は以下の順序で進めていきます。

  1. Spring Boot プロジェクトの作成
  2. Spring Boot プロジェクトのサブプロジェクト化
  3. Vue.js サブプロジェクトの追加
  4. Spring Boot と Vue.js のサブプロジェクト間の連携
  5. プロジェクトのビルドと実行

サンプルでは Spring Boot と Vue.js を使用しますが、他のものであっても Gradle と Node.js のプロジェクトであれば今回の方法は適用可能なはずです。

コマンドは macOS や Linux の書式で記載しますが、パスの指定をスラッシュ (/) からバックスラッシュ (\) に変更すれば Windows でも実行可能です。

Spring Boot プロジェクトの作成

まず Spring Boot の雛形プロジェクトを Spring Initializr で生成します。

https://start.spring.io/ に Web ブラウザでアクセスし、以下のように選択します。(GroupArtifact などは適宜変更いただいて問題ありません。)

spring_initializr_01.jpeg

GENERATE ボタンをクリックしてプロジェクトをダウンロードして展開します。

$ unzip spring-boot-vue-app.zip

プロジェクトのディレクトリ構成は以下のようになっています。

spring_boot_vue_app_dir_01_02.png

初期の Gradle の設定ファイル settings.gradle.ktsbuild.gradle.kts は以下の内容になっています。

settings.gradle.kts
rootProject.name = "spring-boot-vue-app"
build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id("org.springframework.boot") version "2.5.7"
    id("io.spring.dependency-management") version "1.0.11.RELEASE"
    kotlin("jvm") version "1.5.31"
    kotlin("plugin.spring") version "1.5.31"
}

group = "io.aucfan.sample"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11

repositories {
    mavenCentral()
}

dependencies {
    implementation("org.springframework.boot:spring-boot-starter")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "11"
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}

雛形プロジェクトはフラットに 1 つのプロジェクトのみで構成されていますが、これを以下のサブプロジェクトに分割していきます。

spring-boot-vue-app
├── web-flux-server (Spring Boot サブプロジェクト)
│   ├── build.gradle.kts
│   └── src
├── web-vue2-ui (Vue.js サブプロジェクト)
│   └── build.gradle.kts
├── build.gradle.kts
└── settings.gradle.kts

Spring Boot プロジェクトのサブプロジェクト化

初期の Spring Boot プロジェクトを web-flux-server サブプロジェクトに移動します。以下のように web-flux-server ディレクトリを作成し、src ディレクトリを作成したディレクトリ下に移動します。(設定ファイルは YAML 形式で記述したいので、こっそり application.propertiesapplication.yml に変更しています。)

spring_boot_vue_app_dir_02_02.png

settings.gradle.kts の末尾にサブプロジェクトの情報を追記します。

settings.gradle.kts
rootProject.name = "spring-boot-vue-app"

include(
    "web-flux-server",
)

プロジェクト直下の build.gradle.kts を以下の内容と、サブプロジェクト下の web-flux-server/build.gradle.kts に分割します。変更の概要は次のとおりです。

  • org.springframework.bootio.spring.dependency-management のプラグインの行の末尾に apply false を追記
  • groupversion の定義を allprojects 内に移動
  • dependencies 以降を subprojects 内に移動
  • java.sourceCompatibility 行を subprojects 内に移動
  • repositoriessubprojects 内にもコピー
  • subprojects 内で Kotlin 関連のプラグインを apply
  • web-flux-server ディレクトリ内にサブプロジェクト用の build.gradle.kts を作成
  • 親プロジェクト build.gradle.ktssubprojects から Spring 関連の dependenciestasksweb-flux-server/build.gradle.kts へ移動
  • web-flux-server/build.gradle.kts で Spring 関連のプラグインを apply
  • web-flux-server/build.gradle.kts に WebFlux や開発用、デプロイ用の dependenciestasks を追加
build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    // apply false を付加してデフォルトでは術式を発動させないようにする
    id("org.springframework.boot") version "2.5.7" apply false
    id("io.spring.dependency-management") version "1.0.11.RELEASE" apply false
    kotlin("jvm") version "1.5.31"
    kotlin("plugin.spring") version "1.5.31"
}

allprojects {
    group = "io.aucfan.sample"
    version = "0.0.1-SNAPSHOT"
}

repositories {
    mavenCentral()
}

// サブプロジェクト共通設定
subprojects {
    // Kotlin 関連のプラグインを発動させる
    apply(plugin = "kotlin")
    apply(plugin = "org.jetbrains.kotlin.plugin.spring")

    java.sourceCompatibility = JavaVersion.VERSION_11

    repositories {
        mavenCentral()
    }

    dependencies {
        implementation("org.jetbrains.kotlin:kotlin-reflect")
        implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    }

    tasks.withType<KotlinCompile> {
        kotlinOptions {
            freeCompilerArgs = listOf("-Xjsr305=strict")
            jvmTarget = "11"
        }
    }

    tasks.withType<Jar> {
        // JAR ファイル名の基本部分が <プロジェクト名>-<サブプロジェクト名> となるように設定
        archiveBaseName.set(listOf(rootProject.name, project.name).joinToString("-"))
    }
}
web-flux-server/build.gradle.kts
// Spring 関連のプラグインを発動させる
apply(plugin = "org.springframework.boot")
apply(plugin = "io.spring.dependency-management")

dependencies {
    // サブプロジェクトでは developmentOnly がそのままでは呼び出せないので強制召喚
    val developmentOnly = configurations.getByName("developmentOnly")

    // WebFlux に必要
    implementation("org.springframework.boot:spring-boot-starter-webflux")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")

    // 開発ツール
    developmentOnly("org.springframework.boot:spring-boot-devtools")

    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<Test> {
    useJUnitPlatform()
}

// JAR に起動スクリプトを埋め込んで単体で実行可能にする
tasks.withType<org.springframework.boot.gradle.tasks.bundling.BootJar> {
    launchScript()
}

Vue.js サブプロジェクトの追加

Spring Boot プロジェクトをサブプロジェクトに移動できたので、同様に Vue.js サブプロジェクトも追加していきます。

本来であればこちらは Node.js の領域なのですが、Gradle 領域を展開していきます。

Node.js まわりを Gradle で管理するために Gradle Plugin for Node を導入します。

以下のように web-vue2-ui ディレクトリを作成し、サブプロジェクト用の build.gradle.kts を作成します。

spring_boot_vue_app_dir_03_02.png

settings.gradle.ktsweb-vue2-ui サブプロジェクトを追加します。

settings.gradle.kts
rootProject.name = "spring-boot-vue-app"

include(
    "web-flux-server",
    "web-vue2-ui",
)

親プロジェクト直下の build.gradle.ktsGradle Plugin for Nodeapply false で追加します。

build.gradle.kts
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id("org.springframework.boot") version "2.5.7" apply false
    id("io.spring.dependency-management") version "1.0.11.RELEASE" apply false
    // Gradle Plugin for Node を追加 (デフォルトでは発動させない)
    id("com.github.node-gradle.node") version "3.1.1" apply false
    kotlin("jvm") version "1.5.31"
    kotlin("plugin.spring") version "1.5.31"
}

// (後略)

Vue.js サブプロジェクト用の web-vue2-ui/build.gradle.kts に以下を記述します。ここでは、web-vue2-ui/.cache/ ディレクトリ下に Node.js、npm、yarn がダウンロードされて配置されるように設定しています。

web-vue2-ui/build.gradle.kts
import com.github.gradle.node.NodeExtension
import com.github.gradle.node.npm.proxy.ProxySettings

// Gradle Plugin for Node を発動させる
apply(plugin = "com.github.node-gradle.node")

// サブプロジェクトでは node が呼び出せないので強制召喚
configure<NodeExtension> {
    download.set(true)
    version.set("14.18.1")
    npmVersion.set("6.14.15")
    yarnVersion.set("1.22.17")
    distBaseUrl.set("https://nodejs.org/dist")
    npmInstallCommand.set("ci")
    workDir.set(file("${project.projectDir}/.cache/nodejs"))
    npmWorkDir.set(file("${project.projectDir}/.cache/npm"))
    yarnWorkDir.set(file("${project.projectDir}/.cache/yarn"))
    nodeProjectDir.set(file("${project.projectDir}"))
    nodeProxySettings.set(ProxySettings.SMART)
}

Node.js、npm、yarn の本体が .cache ディレクトリに取得されるように Gradle Plugin for Node 経由で yarn コマンドを素振りしておきます。

$ ./gradlew :web-vue2-ui:yarn

以下のように nodejsnpmyarn ディレクトリが設定したとおりに作成され、Node.js まわりを閉じ込めることに成功しました。

spring_boot_vue_app_dir_04_02.png

これで yarn コマンドが呼び出せるようになったので、Vue.js プロジェクトを作成していきます。今回は Vue CLI を使用して Vue.js 2 のシンプルなプロジェクトを作成してみます。一般的には Vue CLI は global にインストールすることがほとんどだと思いますが、ここでは使い捨てのプロジェクト内にインストールします。

以下のように web-vue2-ui ディレクトリに入って、yarn コマンドで使い捨てプロジェクトを作成します。
(Windows の場合は yarn のディレクトリが少し異なり、.cache\yarn\yarn-v1.22.17\yarn となるようです。)

$ cd web-vue2-ui
$ .cache/yarn/yarn-v1.22.17/bin/yarn init
yarn init v1.22.17
question name (web-vue2-ui): spring-boot-vue-app-ui
question version (1.0.0): 0.0.1
question description: Spring Boot + Vue application
question entry point (index.js): 
question repository url: 
question author: 
question license (MIT): 
question private: 
success Saved package.json

作成された使い捨てプロジェクトに Vue CLI を追加します。前述のとおり、ふつうは yarn global add @vue/cli とするのですが、Vue CLI が Gradle 領域の外に逃げてしまうので、ここでは global を指定していません。

$ .cache/yarn/yarn-v1.22.17/bin/yarn add @vue/cli

一時的に vue コマンドが使用できるようになったので、以下のようにシンプルな Vue.js 2 のプロジェクトを作成します。

$ ./node_modules/.bin/vue create spring-boot-vue-app-ui

Vue CLI v4.5.15
? Please pick a preset: Default ([Vue 2] babel, eslint)
? Pick the package manager to use when installing dependencies: Yarn

web-vue2-ui/spring-boot-vue-app-ui 下にプロジェクトが生成されたので、1 階層上に移動させます。その際に不必要になった一時的な使い捨てプロジェクト用の node_modulespackage.jsonyarn.lock は削除してしまいます。また、作成された Vue.js プロジェクト内に Git 用の隠しディレクトリ .git が作成されてしまうので、こちらは移動せずに削除し、.gitignore ファイルのみ移動しています。

$ rm -rf node_modules package.json yarn.lock
$ mv spring-boot-vue-app-ui/* .
$ mv spring-boot-vue-app-ui/.gitignore .
$ rm -rf spring-boot-vue-app-ui

.cache ディレクトリを Git 管理対象外にするために、web-vue2-ui/.gitignore の方に定義を追加しておきます。

web-vue2-ui/.gitignore
.DS_Store
node_modules
/dist
# 以下を追記
/.cache

# (後略)

最終的に web-vue2-ui サブプロジェクトのディレクトリ構成は以下のようになります。 (作成した Vue.js プロジェクトによって構成は変わる場合があります。)

spring_boot_vue_app_dir_05_02.png

vue コマンドで生成された package.json の内容は以下です。

web-vue2-ui/package.json
{
  "name": "spring-boot-vue-app-ui",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "core-js": "^3.6.5",
    "vue": "^2.6.11"
  },
  "devDependencies": {
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-eslint": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "babel-eslint": "^10.1.0",
    "eslint": "^6.7.2",
    "eslint-plugin-vue": "^6.2.2",
    "vue-template-compiler": "^2.6.11"
  },
  "eslintConfig": {
    "root": true,
    "env": {
      "node": true
    },
    "extends": [
      "plugin:vue/essential",
      "eslint:recommended"
    ],
    "parserOptions": {
      "parser": "babel-eslint"
    },
    "rules": {}
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not dead"
  ]
}

開発用のサーバーを起動して動作を確認しておきます。

$ .cache/yarn/yarn-v1.22.17/bin/yarn serve

Web ブラウザで http://localhost:8080/ にアクセスすると以下の画面が表示されます。

vue2_ui_01.jpeg

この後で起動する Spring Boot の Web サーバーもデフォルトでは 8080 ポートを使用するので、衝突を避けるために開発サーバを Ctrl + C で停止しておきます。(./gradlew :web-vue2-ui:yarn_serve でも開発サーバーは起動できますが、Ctrl + C でプロセスが停止できないのでここでは使用していません。)

Spring Boot と Vue.js のサブプロジェクト間の連携

親プロジェクトのディレクトリに戻り、Git リポジトリの初期化を実行しておきます。

$ cd ..
$ git init

web-vue2-ui/package.json を以下のように変更します。

web-vue2-ui/package.json
{
  "name": "spring-boot-vue-app-ui",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve --port 8081 --watch --mode development",
    "build": "vue-cli-service build --dest ../web-flux-server/src/main/resources/static/",
    "lint": "vue-cli-service lint"
  },

  (中略)

}

vue-cli-service serve --port 8081 --watch --mode development の部分で Vue.js の開発サーバーを設定しています。--port 8081 でポート番号を 8080 から 8081 に変更して帳を下ろしています。これは前述のとおり Spring Boot の WebFlux サーバーもデフォルトでは 8080 ポートを使用するので、衝突を避けるためです。--watch でソースファイルの変更を監視するようにし、--mode development で開発環境であることを設定しています。

vue-cli-service build --dest ../web-flux-server/src/main/resources/static/ の部分では Vue.js のビルドファイルの出力先を、デフォルトの dist から Spring Boot サブプロジェクトのリソースディレクトリに変更しています。

また、親プロジェクトのバージョンに合わせて、version0.0.1 に変更しています。

親プロジェクトの .gitignore に以下を追加して、Vue.js ビルドファイルを Git 管理対象外にしておきます。

.gitignore
# (前略)

# Vue.js build files
/web-flux-server/src/main/resources/static/

Gradle Plugin for Node 経由で Vue.js サブプロジェクトをビルドしてみます。

$ ./gradlew :web-vue2-ui:yarn_build

Spring Boot サブプロジェクト側の web-flux-server/src/main/resources/static/ ディレクトリ下に Web ページ用のファイルが生成されます。

spring_boot_vue_app_dir_06_02.png

Vue.js サブプロジェクトがビルドできたので、Gradle タスク間の依存関係を設定します。

まず、yarn build の前に yarn install が実行されるようにします。(毎回 yarn install を実行する必要はないのですが、すでに必要なパッケージがインストールされている場合は何もしないので、ここでは気にせずに設定してしまいます。)

web-vue2-ui/build.gradle.kts
import com.github.gradle.node.NodeExtension
import com.github.gradle.node.npm.proxy.ProxySettings

apply(plugin = "com.github.node-gradle.node")

// サブプロジェクトでは node が呼び出せないので強制召喚
configure<NodeExtension> {
    download.set(true)
    version.set("14.18.1")
    npmVersion.set("6.14.15")
    yarnVersion.set("1.22.17")
    distBaseUrl.set("https://nodejs.org/dist")
    npmInstallCommand.set("ci")
    workDir.set(file("${project.projectDir}/.cache/nodejs"))
    npmWorkDir.set(file("${project.projectDir}/.cache/npm"))
    yarnWorkDir.set(file("${project.projectDir}/.cache/yarn"))
    nodeProjectDir.set(file("${project.projectDir}"))
    nodeProxySettings.set(ProxySettings.SMART)
}

// 以下を追記
// yarn build 前に yarn install を実行する (Gradle Plugin for Node 経由の実行なので _ を付加)
tasks.getByName("yarn_build") {
    dependsOn("yarn_install")
}

次に、Spring Boot サブプロジェクトでリソースが処理される前に Vue.js サブプロジェクトをビルドするようにします。

web-flux-server/build.gradle.kts
import org.springframework.boot.gradle.tasks.bundling.BootJar

apply(plugin = "org.springframework.boot")
apply(plugin = "io.spring.dependency-management")

dependencies {
    // サブプロジェクトでは developmentOnly がそのままでは呼び出せないので強制召喚
    val developmentOnly = configurations.getByName("developmentOnly")
    implementation("org.springframework.boot:spring-boot-starter-webflux")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
    developmentOnly("org.springframework.boot:spring-boot-devtools")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<Test> {
    useJUnitPlatform()
}

tasks.withType<BootJar> {
    launchScript()
}

// 以下を追記
// リソース処理の前に Vue.js サブプロジェクトのビルドを実行
tasks.withType<ProcessResources> {
    dependsOn(":web-vue2-ui:yarn_build")
}

これだけでは Spring Boot サブプロジェクト内のリソースに配置されたファイルを公開できないので、IndexHandler.ktIndexRouterConfiguration.kt ファイルを以下のように作成します。

spring_boot_vue_app_dir_07_02.png

IndexHandler.kt には以下の内容を記述します。

IndexHandler.kt
package io.aucfan.sample.spring.boot.vue

import org.springframework.beans.factory.annotation.Value
import org.springframework.core.io.Resource
import org.springframework.http.MediaType
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import org.springframework.web.reactive.function.server.bodyValueAndAwait

@Component
class IndexHandler {

    @Value("classpath:/static/index.html")
    private lateinit var indexHtml: Resource

    suspend fun index(request: ServerRequest): ServerResponse =
        ServerResponse.ok()
            .contentType(MediaType.TEXT_HTML)
            .bodyValueAndAwait(indexHtml)
}

IndexRouterConfiguration.kt には以下を記述します。

IndexRouterConfiguration.kt
package io.aucfan.sample.spring.boot.vue

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.core.io.ClassPathResource
import org.springframework.web.reactive.function.server.RouterFunctions
import org.springframework.web.reactive.function.server.coRouter

@Configuration
class IndexRouterConfiguration {

    @Bean
    fun indexRouter(indexHandler: IndexHandler) = coRouter {
        GET("/", indexHandler::index)
    }

    fun staticResourceRouter() = RouterFunctions.resources("/**", ClassPathResource("static/"))
}

プロジェクトのビルドと実行

以上で準備ができたので、親プロジェクトのディレクトリで (テストを省略して) ビルドを実行します。

$ ./gradlew clean build -x test

Spring Boot サブプロジェクトの web-flux-serverbuild/libs/ ディレクトリ下に spring-boot-vue-app-web-flux-server-0.0.1-SNAPSHOT.jar が具現化されます。

spring_boot_vue_app_dir_08_02.png

spring-boot-vue-app-web-flux-server-0.0.1-SNAPSHOT.jar を JVM に流し込んで実行してみます。

$ cd web-flux-server/build/libs/
$ java -jar spring-boot-vue-app-web-flux-server-0.0.1-SNAPSHOT.jar

Web ブラウザで http://localhost:8080/ にアクセスすると以下の画面が再度表示されます。(この時点では前述の Vue.js の開発用サーバーは http://localhost:8081/ でのアクセスに変更されています。)

vue2_ui_01.jpeg

ここまでのプロジェクト構築では Spring Boot の WebFlux サーバーに何の Web API も実装されていません。ここから Web API を Kotlin で実装し、Vue.js から axios で利用するように開発を進めていくことになります。

おわりに

今回ご紹介したプロジェクトで作成された Web アプリをデプロイする最もシンプルな方法は以下のような手順になります。

  1. 開発環境に JDK を導入
  2. プロジェクトを git clone
  3. プロジェクトのディレクトリで ./gradlew clean build -x test
  4. 作成された JAR を稼働環境に配置
  5. 稼働環境に JRE を導入
  6. JAR を実行 (サービス化も可能)

実際のプロジェクトではさらに、JAR ファイルの外側から読み込む環境依存の設定ファイルの配置や、Docker イメージにして自動デプロイなどもろもろ追加されていきます。

このように Node.js 関連の儀式を明示的に行うことなく、Gradle 領域内にうまく閉じ込めることができたので、今までどおり心穏やかにデプロイ作業を進めることができています。(とはいえ開発時は Node.js 領域内に入って戦うことになるのですが...)

他にも以下のようなメリットがあるので、今後の SPA プロジェクトでも適している場面があれば採用していこうと思います。

  • 開発環境構築時も同様に Node.js 環境を別途用意する必要がない
  • Vue.js 2 から Vue.js 3 や Nuxt.js、React、Next.js などに変更する場合も別領域サブプロジェクトを展開して対応可能
  • Node.js から見ると領域は外界と隔絶されているので、UI のサブプロジェクト内では異なる Node.js のバージョンを採用可能

最後に...


劇場版「呪術廻戦 0」
12 月 24 日公開です。ぜひ。

21
10
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
21
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?