概要
プログラミングElmを買った。
開発を試すための準備を行ったのでメモを残す。
やること
- webpack5を試す
- 試した結果、webpack4のときと違いなし
- typescriptからElmを呼び出す
- 型付きの世界を知ってしまうともうJSには戻りたくない
- VSCodeで開発しやすくする
- linterは必須。細かいインデント直すのとか嫌
- github.ioに自動デプロイ
- 成果は見れるようにしたい
環境
- Windows 10
- Node 14.16.1
- npm グローバルインストール
- elm-analyse@0.16.5
- elm-format@0.8.5
- elm-test@0.19.1-revision6
- @elm-tooling/elm-language-server@2.0.3
以下、npm installでpackage.jsonのパッケージをインストールしていることを前提で記す。
package.json
package.json
{
"name": "eiseiteien",
"version": "0.0.0",
"description": "elmのサンプル",
"main": "index.js",
"scripts": {
"dev": "webpack",
"prod": "webpack",
"serve": "webpack serve"
},
"repository": {
"type": "git",
"url": "git@ssh.dev.azure.com:v3/hibohiboo/%E8%A1%9B%E6%98%9F%E5%BA%AD%E5%9C%92/aws-elm"
},
"author": "",
"license": "MIT",
"devDependencies": {
"@elm-tooling/elm-language-server": "^2.1.0",
"@typescript-eslint/eslint-plugin": "^4.22.1",
"@typescript-eslint/parser": "^4.22.1",
"css-loader": "^5.2.4",
"elm": "^0.19.1-5",
"elm-format": "^0.8.5",
"elm-hot-webpack-loader": "^1.1.8",
"elm-webpack-loader": "^8.0.0",
"eslint": "^7.25.0",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-webpack": "^0.13.0",
"eslint-plugin-prettier": "^3.4.0",
"html-webpack-plugin": "^5.3.1",
"prettier": "^2.2.1",
"style-loader": "^2.0.0",
"ts-loader": "^9.1.2",
"typescript": "^4.2.4",
"webpack": "^5.36.2",
"webpack-cli": "^4.7.0",
"webpack-dev-server": "^4.0.0-beta.3"
}
}
VSCodeへLinterの導入
- ソースコード保存時にフォーマットされるようにする
- editorconfig
- eslint
- prettier
- elmtooling
- srcフォルダに
~
のエイリアスを切っている。eslint,tsconfig,webpackの3か所に設定が必要*参考
設定ファイル
.vscode/extensions.json
{
"recommendations": [
"editorconfig.editorconfig",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"elmtooling.elm-ls-vscode",
],
"unwantedRecommendations": []
}
.vscode/settings.json
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
},
"editor.formatOnSave": true,
"editor.tabCompletion": "on",
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
// デフォルトの設定はオフにしておく
"typescript.format.enable": false,
"javascript.format.enable": false,
"liveServer.settings.port": 5502,
"[scss]": {
"editor.formatOnSave": false,
"editor.defaultFormatter": "esbenp.prettier-vscode",
},
"css.validate": false,
"scss.validate": false,
}
.editorconfig
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"~/*": ["./src/*"]
},
"outDir": "./dist/",
"sourceMap": true,
"target": "es6",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"noImplicitAny": true,
},
"exclude": ["node_modules", "__tests__", "cdk"],
"include": ["next-env.d.ts", "**/*.ts"]
}
.eslintrc.js
const path = require('path')
module.exports = {
ignorePatterns: ['.eslintrc.js', '.babelrc.js', 'webpack.config.js', 'public'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:prettier/recommended',
'prettier',
],
plugins: ['@typescript-eslint'],
parser: '@typescript-eslint/parser',
env: {
browser: true,
node: true,
es6: true,
jest: true,
},
parserOptions: {
sourceType: 'module',
},
rules: {
'react/prop-types': 'off',
'react/react-in-jsx-scope': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'commma-dangle': 'off',
},
settings: {
'import/resolver': {
webpack: { config: path.join(__dirname, '/webpack.config.js') },
},
},
}
.prettierrc.js
module.exports = {
semi: false,
arrowParens: 'always',
singleQuote: true,
trailingComma: 'all',
}
elm-tooling.json
{
"entrypoints": [
"./src/elm-files/Main.elm"
]
}
フォルダ構造
- public
- template.html
- main.css
- src
- elm-files
- Main.elm
- Main.elm.d.ts
- entry
- index.ts
Webpack
- ts,elmを1つのjsにバンドル
- elmは
npm run dev
,npm run serve
の時はデバッグを有効にする - css,htmlはpublicフォルダ内にあるものを利用
設定ファイル
webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MODE = ['dev', 'serve'].includes(process.env.npm_lifecycle_event)
? 'development'
: 'production'
const elmRuleBase = {
test: /\.elm$/,
exclude: [/elm-stuff/, /node_modules/],
use: [],
}
const elmRule =
MODE === 'development'
? {
...elmRuleBase,
use: [
{ loader: 'elm-hot-webpack-loader' },
{
loader: 'elm-webpack-loader',
options: {
debug: true,
},
},
],
}
: {
...elmRuleBase,
use: [
{
loader: 'elm-webpack-loader',
options: {
optimize: true,
},
},
],
}
module.exports = {
mode: MODE,
entry: './src/entry/index.ts',
devtool: 'source-map', // 'inline-source-map',
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.css/,
use: [
'style-loader',
{
loader: 'css-loader',
options: { url: false },
},
],
},
elmRule,
],
},
resolve: {
alias: {
'~': path.resolve(__dirname, 'src'),
},
extensions: ['.ts', '.js'], // .jsがないと、Can't resolve 'xxxx' が発生する
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
plugins: [
new HtmlWebpackPlugin({
title: 'webpack Boilerplate',
template: path.resolve(__dirname, './public/template.html'),
filename: 'index.html',
}),
],
}
public/template.html
<!DOCTYPE html>
<html lang="ja">
<head>
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div id="root"></div>
</body>
</html>
Elm Hello
- フォーマッタが効くかどうか、
main =text "Html hello!"
を一行にしてみるなどして試す- 効いていない場合、vscodeを再起動してみると直ることが多い
- それでもダメなら、一度
npm run serve
するなどしてelmをトランスパイル後、もう一度VSCodeを再起動
src/elm-files/Main.elm
module Main exposing (main)
import Html exposing (Html, text)
main : Html msg
main =
text "Html hello!"
Typescript EntryPoint
-
;
を入れて保存するなど、eslintが効いていることを確認 - Elmとhtmlのidを紐づける作業
- Elmの型は
.d.ts
の型ファイルを書いてTypescriptに認識させる *参考
src/entry/index.ts
import { Elm } from '~/elm-files/Main.elm'
import '~/../public/main.css'
Elm.Main.init({
node: window.document.getElementById('root'),
})
src/elm-files/Main.elm.d.ts
export namespace Elm {
namespace Main {
interface Args {
node: HTMLElement
flags?: Flags
}
interface Flags {
initialValue: string
}
function init(args: Args)
}
}
動作確認
ホットリロードの確認
-
npm run serve
で起動 - http://localhost:8080/ に アクセス
- Html hello!とテキストが出ていることを確認
- テキストを書き換えて、ホットリロードされることを確認
デバッグログの確認
- ログ出力をソースにいれる
src/elm-files/Main.elm
module Main exposing (main)
import Html exposing (Html, text)
main : Html msg
main =
let
_ =
Debug.log "log" "test"
in
text "Html hello!"
- 開発者ツールのコンソールでログを出力できることを確認
-
npm run dev
でトランスパイルできることを確認 -
npm run prod
でトランスパイルが失敗することを確認。Elmのエラーメッセージはとても丁寧。
Github.ioへのデプロイ
Github Actionsを利用する。
actions用のyamlファイルを作成。
deployブランチを切ってpush
Actionファイル
.github/workflows/gh-pages.yml
name: github pages
on:
push:
branches:
- deploy # Set a branch name to trigger deployment
jobs:
deploy:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: '14'
- name: modules Setup
run: npm i
working-directory: .
- name: trancepile
run: npm run dev
working-directory: .
- name: Move files
run: |
mv ./dist ./docs
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs
- gh-pagesブランチが作成されていることを確認
- SettingsからGitHub Pagesの有効化を行う
- ブランチはgh-pages,フォルダは(/root)を選択
- 表示されるURLにアクセスし、ページが表示されることを確認する。
参考
『プログラミングElm』サポートサイト
React: import時のaliasを設定するときはWebpack、TypeScript、ESLintの3つを対応しなければならない件
TypeScript から Elm を import する時にエラーを出さない方法 (Parcel)
GitHub Actionsを使ってGithub PagesにOpen APIのドキュメントを公開したメモ