clojure之制作lein模板(一)


使用开源luminus模板策略,制作属于自己的项目模板

简书地址

传送门

Writing Templates
Writing Lein template — quick tutorial

制作目标项目

  1. 创建工程
➜  lein new template hc-template --to-dir hc-template
Generating a Luminus project.
➜  cd hc-template
➜  hc-template tree
.
├── CHANGELOG.md
├── LICENSE
├── README.md
├── project.clj
├── resources
│   └── leiningen
│       └── new
│           └── hc_template
│               └── foo.clj
└── src
    └── leiningen
        └── new
            └── hc_template.clj

7 directories, 6 files
➜  
  1. 根目录手动增加shadow-cljs.edn文件
;; This file is generated by lein-shadow, do not manually edit. Instead, edit project.clj shadow-cljs key.
{:nrepl {:port 7002},
 :builds
 {:app
  {:target :browser,
   :output-dir "target/cljsbuild/public/js",
   :asset-path "/js",
   :modules {:app
             {:entries [hc-template.app]}},
   :devtools {:watch-dir "resources/public",
              :preloads [re-frisk.preload]},
   :dev {:closure-defines {"re_frame.trace.trace_enabled_QMARK_" true}}
   :release {:output-dir "dist/"
             :module-hash-names true
             :build-options {:manifest-name "cljs-manifest.json"}}}} ,
 
 :test {:target :node-test,
        :output-to "target/test/test.js",
        :autorun true}},
 :dev-http {8000 {:roots ["resources/public" "target/cljsbuild/public"]}}
 :lein true}
  1. 引入antd
npm install antd --save

package.json配置文件

{
  "dependencies": {
    "antd": "^3.22.0",
    "create-react-class": "15.6.3",
    "react": "16.8.6",
    "react-dom": "16.8.6",
    "shadow-cljs": "2.8.39"
  },
  "devDependencies": {}
}

4、配置入口文件
resources/public目录下新建index.html文件,内容如下:

<!DOCTYPE html>
<html lang="cn">
<head>
    <title>后台管理系统</title>
    <meta charset="utf-8"/>
    <meta content="width=device-width, initial-scale=1.0" name="viewport" />
    <link href="https://cdn.bootcss.com/antd/3.18.0/antd.min.css" rel="stylesheet">
</head>
<body>
<!-- Our JavaScript will modify the DOM inside this element -->
<div id="app"></div>
<!-- All our ClojureScript gets compiled into this file -->
<script>
    document.write("<script type='text/javascript' src='/js/app.js?v="+Math.random()+"' type='text/javascript'><\/script>");
</script>
</body>
</html>
  1. 启动项目,验证是否正常
➜  shadow-cljs server
Preparing npm packages
Installing npm packages
npm packages successfully installed
Running shadow-cljs...
2019-08-25 20:13:00,880 [main] DEBUG org.jboss.logging - Logging Provider: org.jboss.logging.Slf4jLoggerProvider
2019-08-25 20:13:02,199 [main] DEBUG io.undertow - starting undertow server io.undertow.Undertow@31ff0947
2019-08-25 20:13:02,207 [main] INFO  org.xnio - XNIO version 3.7.0.Final
2019-08-25 20:13:02,398 [main] INFO  org.jboss.threads - JBoss Threads version 2.3.2.Final
2019-08-25 20:13:02,423 [main] DEBUG io.undertow - Configuring listener with protocol HTTP for interface 0.0.0.0 and port 9630
shadow-cljs - server version: 2.8.39 running at http://localhost:9630
shadow-cljs - nREPL server started on port 7002
shadow-cljs - watching build :app
[:app] Configuring build.
[:app] Compiling ...
[:app] Build completed. (345 files, 344 compiled, 0 warnings, 62.36s)

参考luminus-template实现机制编写source加载代码

参考leiningen的原因是Writing Lein template — quick tutorial这文中妹子没有考虑clojure本身两个大括号{{xxx}}这种语法是存在的,所以如果用她的方式,代码中本来就是{{xxx}}的语法将创建模板不成功,我想这也是leiningen使用连个尖括号<<name>>来给变量赋值的原因。

上核心代码:

(ns leiningen.new.common
  (:require
   [selmer.parser :as selmer]
   [leiningen.new.templates :refer [renderer raw-resourcer ->files]]
   [clojure.pprint :refer [code-dispatch pprint with-pprint-dispatch]]
   [clojure.string :as string]
   [clojure.java.io :as io]))

(def template-name "hc-template")

(defn render-template [template options]
  (selmer/render
   (str "<% safe %>" template "<% endsafe %>")
   options
   {:tag-open \< :tag-close \> :filter-open \< :filter-close \>}))

(defn init-render []
  (renderer template-name render-template))

(defn slurp-resource [path]
  (-> (str "leiningen/new/hc_template/" path)
      io/resource
      slurp))

(selmer/add-tag!
 :include
 (fn [args context-map]
   (-> (slurp-resource (first args))
       (render-template context-map)
       (string/replace #"^\n+" "")
       (string/replace #"\n+$" ""))))

(defn render-asset [render options asset]
  (if (string? asset)
    asset
    (let [[target source] asset]
      [target (render source options)])))

(defn render-assets [assets binary-assets options]
  (let [render (init-render)
        raw (raw-resourcer template-name)]
    (apply ->files options
           (into
            (map (partial render-asset render options) assets)
            (map (fn [[target source]] [target (raw source)]) binary-assets)))))

需要加载的资源文件目录是sources/leiningen/new/hc_template目录下,加载文件:

(ns leiningen.new.hc-template
  (:require [leiningen.new.templates :refer [multi-segment sanitize-ns renderer 
                                             name-to-path ->files project-name year sanitize]]
            [leiningen.core.main :as main]
            [selmer.parser :as selmer]
            [clojure.string :as string]
            [leiningen.new.common :refer :all]
            [clojure.java.io :as io]))

(def timestamp (.format
                (java.text.SimpleDateFormat. "yyyyMMddHHmmss")
                (java.util.Date.)))

(def project-assets
  [["dev-config.edn"    "dev-config.edn"]
   [".gitignore"        "gitignore" ]
   ["Procfile"          "Procfile" ]
   ["project.clj"       "project.clj" ]
   ["Dockerfile"        "Dockerfile" ]
   ["Capstanfile"       "Capstanfile" ]
   ["README.md"         "README.md"]
   ["shadow-cljs.edn"   "shadow-cljs.edn" ]
   ["package.json"      "package.json" ]])

(def clj-core-assets
  [["{{backend-path}}/{{sanitized}}/core.clj"             "src/clj/core.clj"]
   ["{{backend-path}}/{{sanitized}}/nrepl.clj"            "src/clj/nrepl.clj" ]
   ["{{backend-path}}/{{sanitized}}/config.clj"           "src/clj/config.clj"]
   ["{{backend-path}}/{{sanitized}}/handler.clj"          "src/clj/handler.clj"]
   ["{{backend-path}}/{{sanitized}}/middleware.clj"       "src/clj/middleware.clj"]
   ["{{backend-path}}/{{sanitized}}/middleware/formats.clj"     "src/clj/middleware/formats.clj"]
   ["{{backend-path}}/{{sanitized}}/middleware/exception.clj"   "src/clj/middleware/exception.clj"]
   ["{{backend-path}}/{{sanitized}}/db/core.clj"             "src/clj/db/core.clj"]
   ["{{backend-path}}/{{sanitized}}/routes/services.clj"     "src/clj/routes/services.clj"]
   ["{{backend-path}}/{{sanitized}}/routes/guestbook.clj"     "src/clj/routes/guestbook.clj"]
   ;;test
   ["{{backend-test-path}}/{{sanitized}}/test/handler.clj"   "test/clj/handler.clj"]
   ["{{backend-test-path}}/{{sanitized}}/test/db/core.clj"   "test/clj/db/core.clj"]
   ;; hc clj
   ["{{backend-path}}/{{sanitized}}/db/redis.clj"            "src/clj/db/redis.clj"]
   ["{{backend-path}}/{{sanitized}}/common/result.clj"            "src/clj/common/result.clj"]])

;;这里还有其他很多代码。。。。。
(def db-assets
  [[(str "{{resource-path}}/migrations/" timestamp "-add-users-table.down.sql")   "resources/migrations/20190831145908-add-users-table.down.sql"]
   [(str "{{resource-path}}/migrations/" timestamp "-add-users-table.up.sql")   "resources/migrations/20190831145908-add-users-table.up.sql"]
   ["{{resource-path}}/sql/queries.sql"   "resources/sql/queries.sql"]])


(def binary-assets
  [["{{resource-path}}/public/favicon.ico"  "resources/public/favicon.ico"]
   ["{{resource-path}}/public/index.html"   "resources/public/index.html"]
   ["{{resource-path}}/public/img/warning_clojure.png"  "resources/public/img/warning_clojure.png"]])

(def core-assets
  (vec (concat project-assets
               clj-core-aeests
               environment-assets
               db-assets
               cljs-core-assets
               system-assets)))

(def project-relative-paths
  {:backend-path      "src/clj"
   :backend-test-path "test/clj"
   :client-path       "src/cljs"
   :client-test-path  "test/cljs"
   :resource-path     "resources"
   :cljc-path         "src/cljc"
   :db-path           "src/clj"
   :source-paths      ["src/clj"]
   :resource-paths    ["resources"]
   :now               (java.util.Date.)})

(def render (renderer "hc-template" render-template))

(defn generate-project
  "Create a new Luminus project"
  [options]
  (main/info "Generating a hc-template project.")
  (main/info "Please read README.md firstly!!!")  
  (render-assets core-assets binary-assets  options))

(defn hc-template
  "init function"
  [name]
  (let [options (merge
                 project-relative-paths
                 {:name             (project-name name)
                  :selmer-renderer  render-template
                  :min-lein-version "2.0.0"
                  :project-ns       (sanitize-ns name)
                  :sanitized        (name-to-path name)
                  :year             (year)
                  })]
    (generate-project options)))

模板开发和测试

源代码目录: resources/leiningen/new/hc_template/
加载模板文件:src/leiningen/new/hc_template.clj

开发步骤:

  1. 将自己的代码文件保存在resources/leiningen/new/hc_template/对应的目录下。
  2. 修改文件的namespace名称为待赋值的<<project-ns>>,需要引入的其他变量也一样用尖括号括起来,比如项目名称<>。
  3. 将自己的源代码在src/leiningen/new/hc_template.clj 加入到render队列里。
  4. cd到模板项目rcclojuretemplate,执行如下命令
    lein new hc-template test
    
  5. testing 目录则为用模板创建的目标工程
  6. 运行前端,分别执行
    yarn 
    
    启动
    yarn start
    
    启动后在浏览器本地9630端口查看项目编译情况,8000端口查看前端页面是否加载正常
  7. 运行后端:通过ide启动后台项目,查看能否正常启动,查看3000端口swagger显示否能正常显示
  8. 前后台都测试通过后,删除testing目录,提交源代码。
  9. 部署更新:修改根目录下project.clj里的版本号,最后一位+1即可,
    通过命令lein deploy部署最新版,账号密码:marvin/Mw99267@,首次部署,会有release版本授权错误提示,需要安装gpg,关于gpg的使用,请参考
  1. 如果本地更新测试不充分或者不想配置gpg,可以先push代码,找马海强发布新版本。

评论
  目录