├── README.md ├── spin └── template ├── .gitattributes ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bsconfig.json ├── config ├── postcss.config.js ├── tailwind.config.js └── webpack.config.js ├── package.json ├── public ├── apple-touch-icon.png ├── favicon-16x16.png ├── favicon-32x32.png ├── index.html ├── manifest.json └── robots.txt ├── src ├── App.re ├── App.res ├── Index.re ├── Index.res ├── Route.re ├── Route.rei ├── Route.res ├── Route.resi ├── Router.re ├── Router.res ├── components │ ├── Greet.re │ └── Greet.res ├── pages │ ├── PageHome.re │ ├── PageHome.res │ ├── PageNotFound.re │ └── PageNotFound.res └── styles.css └── tests ├── PageHome_test.re ├── PageHome_test.res └── support ├── Utils.re └── Utils.res /README.md: -------------------------------------------------------------------------------- 1 | # Spin ReScript 2 | 3 | A [Spin](https://github.com/tmattio/spin) template to generate new ReScript project. 4 | 5 | ```bash 6 | spin new https://github.com/tmattio/spin-rescript.git 7 | ``` 8 | 9 | ## Acknowledgments 10 | 11 | This template is inspired by these awesome projects: 12 | 13 | - [create-react-app](https://github.com/facebook/create-react-app) - Set up a modern web app by running one command. 14 | 15 | And these amazing articles: 16 | 17 | - [ReasonML: Safe Routing](https://blog.minima.app/posts/2020/reasonml-safe-routing) -------------------------------------------------------------------------------- /spin: -------------------------------------------------------------------------------- 1 | (name spin-rescript) 2 | (description "ReScript application") 3 | 4 | (config project_name 5 | (input (prompt "Project name"))) 6 | 7 | (config project_slug 8 | (input (prompt "Project slug")) 9 | (default (slugify :project_name)) 10 | (rules 11 | ("The project slug must be lowercase and contain ASCII characters and '-' only." 12 | (eq :project_slug (slugify :project_slug))))) 13 | 14 | (config project_snake 15 | (default (snake_case :project_slug))) 16 | 17 | (config project_description 18 | (input (prompt "Description")) 19 | (default "A short, but powerful statement about your project")) 20 | 21 | (config username 22 | (input (prompt "Name of the author"))) 23 | 24 | (config syntax 25 | (select 26 | (prompt "Which syntax do you use?") 27 | (values ReScript Reason)) 28 | (default ReScript)) 29 | 30 | (config css_framework 31 | (select 32 | (prompt "Which CSS framework do you use?") 33 | (values TailwindCSS None)) 34 | (default None)) 35 | 36 | (config ci_cd 37 | (select 38 | (prompt "Which CI/CD do you use?") 39 | (values Github None)) 40 | (default Github)) 41 | 42 | (ignore 43 | (files config/postcss.config.js config/tailwind.config.js) 44 | (enabled_if (neq :css_framework TailwindCSS))) 45 | 46 | (ignore 47 | (files .github/*) 48 | (enabled_if (neq :ci_cd Github))) 49 | 50 | (ignore 51 | (files */*.re */*.rei) 52 | (enabled_if (neq :syntax Reason))) 53 | 54 | (ignore 55 | (files */*.res */*.resi) 56 | (enabled_if (neq :syntax ReScript))) 57 | 58 | (post_gen 59 | (actions 60 | (run yarn install)) 61 | (message "🎁 Installing packages. This might take a couple minutes.") 62 | (enabled_if (not (run which yarn)))) 63 | 64 | (post_gen 65 | (actions 66 | (run npm install)) 67 | (message "🎁 Installing packages. This might take a couple minutes.") 68 | (enabled_if (run which yarn))) 69 | 70 | (example_commands 71 | (commands 72 | ("yarn start" "Start the development server.") 73 | ("yarn build" "Bundle the app into static files for production.") 74 | ("yarn test" "Start the test runner.")) 75 | (enabled_if (not (run which yarn)))) 76 | 77 | (example_commands 78 | (commands 79 | ("npm start" "Start the development server.") 80 | ("npm build" "Bundle the app into static files for production.") 81 | ("npm test" "Start the test runner.")) 82 | (enabled_if (run which yarn))) 83 | -------------------------------------------------------------------------------- /template/.gitattributes: -------------------------------------------------------------------------------- 1 | # Supresses the lock folder from the diffs 2 | yarn.lock linguist-generated=true 3 | package-lock.json linguist-generated=true 4 | 5 | # Tell github that .re and .rei files are Reason 6 | *.re linguist-language=Reason 7 | *.rei linguist-language=Reason 8 | 9 | # Disable syntax detection for .spin 10 | .spin linguist-language=Text 11 | 12 | # Declare shell files to have LF endings on checkout 13 | # On Windows, the default git setting for `core.autocrlf` 14 | # means that when checking out code, LF endings get converted 15 | # to CRLF. This causes problems for shell scripts, as bash 16 | # gets choked up on the extra `\r` character. 17 | * text eol=lf 18 | -------------------------------------------------------------------------------- /template/.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | {% raw -%} 2 | name: CI 3 | 4 | on: [push, pull_request] 5 | 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v2 12 | 13 | - name: Setup Node.js 14 | uses: actions/setup-node@v1 15 | with: 16 | node-version: 12 17 | 18 | - name: Get yarn cache 19 | id: yarn-cache 20 | run: echo "::set-output name=dir::$(yarn cache dir)" 21 | 22 | - uses: actions/cache@v1 23 | with: 24 | path: ${{ steps.yarn-cache.outputs.dir }} 25 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 26 | restore-keys: | 27 | ${{ runner.os }}-yarn- 28 | 29 | - name: Install dependencies 30 | run: yarn install --frozen-lockfile 31 | 32 | - name: Check formatting 33 | run: | 34 | yarn format 35 | if [ -n "$(git status --porcelain)" ]; then 36 | echo "There are differences:" 37 | git status --porcelain 38 | echo "----" 39 | echo "" 40 | echo 'Please run `yarn format` locally to fix the issues.' 41 | exit 1 42 | fi 43 | 44 | - name: Run tests 45 | run: yarn test 46 | 47 | build: 48 | runs-on: ubuntu-latest 49 | steps: 50 | - name: Checkout 51 | uses: actions/checkout@v2 52 | 53 | - name: Setup Node.js 54 | uses: actions/setup-node@v1 55 | with: 56 | node-version: 12 57 | 58 | - name: Get yarn cache 59 | id: yarn-cache 60 | run: echo "::set-output name=dir::$(yarn cache dir)" 61 | 62 | - uses: actions/cache@v1 63 | with: 64 | path: ${{ steps.yarn-cache.outputs.dir }} 65 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 66 | restore-keys: | 67 | ${{ runner.os }}-yarn- 68 | 69 | - name: Install dependencies 70 | run: yarn install --frozen-lockfile 71 | 72 | - name: Build production assets 73 | run: yarn build 74 | {% endraw %} -------------------------------------------------------------------------------- /template/.gitignore: -------------------------------------------------------------------------------- 1 | # Normal npm stuff 2 | npm-debug.log 3 | /node_modules/ 4 | /.cache/ 5 | /dist/ 6 | /build/ 7 | 8 | # Bucklescript stuff 9 | /lib 10 | /types 11 | .merlin 12 | .bsb.lock 13 | *.bs.js 14 | -------------------------------------------------------------------------------- /template/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Setup your development environment 4 | 5 | All the dependencies can be install via your favorite package manager: 6 | 7 | ```bash 8 | yarn install 9 | # Or 10 | npm install 11 | ``` 12 | 13 | That's it! You're up and running, you can start the project with: 14 | 15 | ```bash 16 | yarn start 17 | # Or 18 | npm run start 19 | ``` 20 | 21 | ### Running Tests 22 | 23 | This project uses Jest as a test framework. You can run the tests of the project with: 24 | 25 | ```bash 26 | yarn test 27 | # Or 28 | npm run test 29 | ``` 30 | 31 | ### Creating production builds 32 | 33 | To create a production build of the application, you can run: 34 | 35 | ```bash 36 | yarn build 37 | # Or 38 | npm run build 39 | ``` 40 | 41 | This will output the compiled files in `build/`. 42 | 43 | ### Repository Structure 44 | 45 | The following snippet describes {{ project_name }}'s repository structure. 46 | 47 | ```text 48 | . 49 | ├── config/ 50 | | Configuration files used to build the project, such as the webpack configuration. 51 | │ 52 | ├── public/ 53 | | Static assets that you want to include when serving your application. 54 | │ The content of this folder will get copied to the production build. 55 | │ 56 | ├── src/ 57 | | Source code of the project application. 58 | │ 59 | ├── tests/ 60 | | Unit tests of the project. 61 | │ 62 | ├── LICENSE 63 | │ 64 | ├── package.json 65 | │ 66 | └── README.md 67 | ``` 68 | -------------------------------------------------------------------------------- /template/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 {{ username }} 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /template/README.md: -------------------------------------------------------------------------------- 1 | # {{ project_name }} 2 | 3 | {%- if ci_cd == 'Github' %} 4 | 5 | [![Actions Status](https://github.com/{{ github_username }}/{{ project_slug }}/workflows/CI/badge.svg)](https://github.com/{{ github_username }}/{{ project_slug }}/actions) 6 | {%- endif %} 7 | 8 | {%- if project_description %} 9 | 10 | {{ project_description }} 11 | {%- endif %} 12 | 13 | ## Contributing 14 | 15 | Take a look at our [Contributing Guide](CONTRIBUTING.md). 16 | -------------------------------------------------------------------------------- /template/bsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{ project_slug }}", 3 | "reason": { 4 | "react-jsx": 3 5 | }, 6 | "sources": [ 7 | { 8 | "dir": "src", 9 | "subdirs": true 10 | }, 11 | { 12 | "dir": "tests", 13 | "subdirs": true, 14 | "type": "dev" 15 | } 16 | ], 17 | "package-specs": [ 18 | { 19 | "module": "commonjs", 20 | "in-source": true 21 | } 22 | ], 23 | "suffix": ".bs.js", 24 | "namespace": true, 25 | "bs-dependencies": [ 26 | {% if css_framework == 'None' %}"bs-css", 27 | "bs-css-emotion", 28 | {% endif -%} 29 | "reason-react" 30 | ], 31 | "bs-dev-dependencies": [ 32 | "@glennsl/bs-jest", 33 | "bs-react-testing-library", 34 | "bs-webapi" 35 | ], 36 | "ppx-flags": [], 37 | "refmt": 3, 38 | "warnings": { 39 | "number": "+A-48-42", 40 | "error": "+A-3-44-102" 41 | }, 42 | "bsc-flags": [ 43 | "-bs-super-errors", 44 | "-bs-no-version-header", 45 | "-open Belt" 46 | ] 47 | } -------------------------------------------------------------------------------- /template/config/postcss.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | plugins: [ 5 | require("tailwindcss")(path.join(__dirname, "tailwind.config.js")), 6 | require("autoprefixer"), 7 | ...(process.env.NODE_ENV === "production" ? [require("cssnano")] : []), 8 | ], 9 | }; 10 | -------------------------------------------------------------------------------- /template/config/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: { 3 | enabled: process.env.NODE_ENV === 'production', 4 | mode: 'all', 5 | content: [ 6 | '../src/**/*.re', 7 | '../public/index.html' 8 | ], 9 | }, 10 | theme: { 11 | extend: { 12 | fontFamily: { 13 | sans: ['Inter var'], 14 | }, 15 | }, 16 | }, 17 | variants: {}, 18 | plugins: [], 19 | } -------------------------------------------------------------------------------- /template/config/webpack.config.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const HtmlWebpackPlugin = require('html-webpack-plugin') 4 | const TerserPlugin = require('terser-webpack-plugin') 5 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 6 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin') 7 | 8 | const appDirectory = fs.realpathSync(process.cwd()) 9 | const paths = { 10 | appPublic: path.resolve(appDirectory, 'public'), 11 | appBuild: path.resolve(appDirectory, 'build'), 12 | appPublic: path.resolve(appDirectory, 'public'), 13 | appHtml: path.resolve(appDirectory, 'public/index.html'), 14 | appIndexJs: path.resolve(appDirectory, 'src/Index.bs'), 15 | appConfig: path.resolve(appDirectory, 'config/'), 16 | appSrc: path.resolve(appDirectory, 'src'), 17 | appNodeModules: path.resolve(appDirectory, 'node_modules'), 18 | } 19 | 20 | const isEnvProduction = process.env.NODE_ENV === 'production' 21 | const isEnvDevelopment = !isEnvProduction 22 | 23 | // This is the production and development configuration. 24 | // It is focused on developer experience, fast rebuilds, and a minimal bundle. 25 | module.exports = { 26 | mode: isEnvProduction ? 'production' : 'development', 27 | // Stop compilation early in production 28 | bail: isEnvProduction, 29 | devtool: isEnvProduction ? 'source-map' : 'cheap-module-source-map', 30 | entry: paths.appIndexJs, 31 | output: { 32 | // There will be one main bundle, and one file per asynchronous chunk. 33 | // In development, it does not produce real files. 34 | filename: isEnvProduction 35 | ? 'static/js/[name].[contenthash:8].js' 36 | : 'static/js/bundle.js', 37 | // TODO: remove this when upgrading to webpack 5 38 | futureEmitAssets: true, 39 | // There are also additional JS chunk files if you use code splitting. 40 | chunkFilename: isEnvProduction 41 | ? 'static/js/[name].[contenthash:8].chunk.js' 42 | : 'static/js/[name].chunk.js', 43 | // The build folder. 44 | path: isEnvProduction ? paths.appBuild : undefined, 45 | // Add /* filename */ comments to generated require()s in the output. 46 | pathinfo: isEnvDevelopment, 47 | publicPath: '/', 48 | }, 49 | optimization: { 50 | minimize: isEnvProduction, 51 | // Automatically split vendor and commons 52 | // https://twitter.com/wSokra/status/969633336732905474 53 | // https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366 54 | splitChunks: { 55 | chunks: 'all', 56 | // name: false, 57 | }, 58 | // Keep the runtime chunk separated to enable long term caching 59 | // https://twitter.com/wSokra/status/969679223278505985 60 | // https://github.com/facebook/create-react-app/issues/5358 61 | runtimeChunk: { 62 | name: entrypoint => `runtime-${entrypoint.name}`, 63 | }, 64 | minimizer: [ 65 | // This is only used in production mode 66 | new TerserPlugin({ 67 | terserOptions: { 68 | parse: { 69 | // We want terser to parse ecma 8 code. However, we don't want it 70 | // to apply any minification steps that turns valid ecma 5 code 71 | // into invalid ecma 5 code. This is why the 'compress' and 'output' 72 | // sections only apply transformations that are ecma 5 safe 73 | // https://github.com/facebook/create-react-app/pull/4234 74 | ecma: 8, 75 | }, 76 | compress: { 77 | ecma: 5, 78 | warnings: false, 79 | // Disabled because of an issue with Uglify breaking seemingly valid code: 80 | // https://github.com/facebook/create-react-app/issues/2376 81 | // Pending further investigation: 82 | // https://github.com/mishoo/UglifyJS2/issues/2011 83 | comparisons: false, 84 | // Disabled because of an issue with Terser breaking valid code: 85 | // https://github.com/facebook/create-react-app/issues/5250 86 | // Pending further investigation: 87 | // https://github.com/terser-js/terser/issues/120 88 | inline: 2, 89 | }, 90 | mangle: { 91 | safari10: true, 92 | }, 93 | output: { 94 | ecma: 5, 95 | comments: false, 96 | // Turned on because emoji and regex is not minified properly using default 97 | // https://github.com/facebook/create-react-app/issues/2488 98 | ascii_only: true, 99 | }, 100 | }, 101 | sourceMap: true, 102 | }), 103 | // This is only used in production mode 104 | new OptimizeCSSAssetsPlugin({ 105 | cssProcessorOptions: { 106 | map: { 107 | // `inline: false` forces the sourcemap to be output into a 108 | // separate file 109 | inline: false, 110 | // `annotation: true` appends the sourceMappingURL to the end of 111 | // the css file, helping the browser find the sourcemap 112 | annotation: true, 113 | } 114 | }, 115 | }), 116 | ] 117 | }, 118 | module: { 119 | rules: [ 120 | { 121 | test: /\.css$/, 122 | use: [ 123 | isEnvDevelopment && 'style-loader', 124 | isEnvProduction && { 125 | loader: MiniCssExtractPlugin.loader, 126 | }, 127 | { 128 | loader: 'css-loader', 129 | options: { 130 | importLoaders: 1, 131 | sourceMap: isEnvProduction, 132 | }, 133 | }, 134 | {% if css_framework == 'TailwindCSS' %}{ 135 | loader: 'postcss-loader', 136 | options: { 137 | postcssOptions: { config: path.resolve(paths.appConfig, 'postcss.config.js') }, 138 | sourceMap: isEnvProduction 139 | }, 140 | }, 141 | {% endif -%} 142 | ].filter(Boolean) 143 | } 144 | ] 145 | }, 146 | plugins: [ 147 | // Generates an `index.html` file with the