├── config ├── environment │ ├── .env │ ├── .env.development │ └── .env.production └── webpack │ ├── addons │ ├── webpack.bundlevisualizer.js │ └── webpack.bundleanalyze.js │ ├── webpack.common.js │ ├── paths.js │ ├── webpack.development.js │ ├── webpack.production.js │ └── rules.js ├── src ├── images │ └── favicon.png ├── view │ ├── components │ │ ├── page-layout │ │ │ ├── styles.scss │ │ │ └── index.js │ │ ├── splash-loader │ │ │ ├── index.js │ │ │ └── styles.scss │ │ ├── maintenance-layout │ │ │ ├── styles.scss │ │ │ └── index.js │ │ └── sample-component │ │ │ └── index.js │ ├── pages │ │ └── landing-page.js │ └── routes.js ├── index.html ├── app.scss └── index.js ├── prettier.config.js ├── .gitignore ├── _config.yml ├── babel.config.js ├── webpack.config.js ├── .eslintrc.js ├── LICENSE ├── package.json └── README.md /config/environment/.env: -------------------------------------------------------------------------------- 1 | BASE_URL=http://localhost.base.com -------------------------------------------------------------------------------- /config/environment/.env.development: -------------------------------------------------------------------------------- 1 | BASE_URL=http://localhost.dev.com 2 | BASE_URL_EXPAND=$BASE_URL/expanded -------------------------------------------------------------------------------- /config/environment/.env.production: -------------------------------------------------------------------------------- 1 | BASE_URL=http://localhost.prod.com 2 | BASE_URL_EXPAND=$BASE_URL/expanded -------------------------------------------------------------------------------- /src/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dhinesh03/mithril-starter-kit/HEAD/src/images/favicon.png -------------------------------------------------------------------------------- /src/view/components/page-layout/styles.scss: -------------------------------------------------------------------------------- 1 | .page-layout { 2 | max-width: 800px; 3 | margin: 1em auto; 4 | } -------------------------------------------------------------------------------- /config/webpack/addons/webpack.bundlevisualizer.js: -------------------------------------------------------------------------------- 1 | const Visualizer = require('webpack-visualizer-plugin'); 2 | 3 | module.exports = { 4 | plugins: [ 5 | new Visualizer() 6 | ] 7 | }; -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 110, 3 | singleQuote: true, 4 | tabWidth: 4, 5 | arrowParens: 'always', 6 | semi: true, 7 | trailingComma: 'es5', 8 | }; 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | 4 | # dependencies 5 | /node_modules 6 | /bower_components 7 | 8 | # misc 9 | .sass-cache 10 | .eslintcache 11 | npm-debug.log* 12 | 13 | # mac 14 | .DS_Store -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mithril Starter Kit 5 | 6 | 7 | 8 |
9 | 10 |
11 | 12 | 13 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman 2 | show_downloads: true 3 | title: Mithril Starter Kit 4 | description: A boilerplate Mithril application using ES6, Babel, Webpack 3, Sass/SCSS, Webpack dev server hot reload and eslint 5 | author: Dhinesh Kumar 6 | logo: /src/styles/images/favicon.png 7 | plugins: 8 | - jekyll-seo-tag 9 | -------------------------------------------------------------------------------- /src/view/components/page-layout/index.js: -------------------------------------------------------------------------------- 1 | import './styles.scss'; 2 | 3 | /** 4 | * A component that wraps another component with some common 5 | * page layout markup and styles. 6 | */ 7 | export default function() { 8 | return { 9 | view: vnode => m('.page-layout', vnode.children), 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /config/webpack/addons/webpack.bundleanalyze.js: -------------------------------------------------------------------------------- 1 | const WebpackBundleAnalyzer = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 2 | const paths = require('../paths'); 3 | module.exports = { 4 | plugins: [ 5 | new WebpackBundleAnalyzer({ 6 | analyzerMode: 'static', 7 | reportFilename: paths.bundleReportPath, 8 | openAnalyzer: false 9 | }) 10 | ] 11 | }; -------------------------------------------------------------------------------- /src/app.scss: -------------------------------------------------------------------------------- 1 | @import-normalize;/* bring in normalize.css styles */ 2 | 3 | html, body, #root { 4 | position: relative; 5 | margin: 0; 6 | width: 100%; 7 | height: 100%; 8 | } 9 | 10 | body, #root { 11 | font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; 12 | font-size: 16px; 13 | line-height: 1.4; 14 | color: #333; 15 | background-color: #FFFFFF; 16 | overflow-x: hidden; 17 | } 18 | -------------------------------------------------------------------------------- /src/view/components/splash-loader/index.js: -------------------------------------------------------------------------------- 1 | import './styles.scss'; 2 | 3 | export default { 4 | view(/*vnode*/) { 5 | return m('.holder', [ 6 | m('.preloader', [ 7 | m('div'), 8 | m('div'), 9 | m('div'), 10 | m('div'), 11 | m('div'), 12 | m('div'), 13 | m('div'), 14 | ]), 15 | ]); 16 | }, 17 | }; -------------------------------------------------------------------------------- /src/view/components/maintenance-layout/styles.scss: -------------------------------------------------------------------------------- 1 | .maintenance { 2 | text-align: center; 3 | padding: 150px; 4 | font: 20px Helvetica, sans-serif; 5 | color: #333; 6 | h1 { 7 | font-size: 50px; 8 | } 9 | article { 10 | display: block; 11 | text-align: left; 12 | width: 650px; 13 | margin: 0 auto; 14 | } 15 | a { 16 | color: #dc8100; 17 | text-decoration: none; 18 | } 19 | a:hover { 20 | color: #333; 21 | text-decoration: none; 22 | } 23 | } -------------------------------------------------------------------------------- /src/view/components/sample-component/index.js: -------------------------------------------------------------------------------- 1 | export default function() { 2 | var count = 0; // added a variable 3 | return { 4 | view: function() { 5 | return m('main', [ 6 | m('h1', { 7 | class: 'title', 8 | }, 'My first component'), 9 | // changed the next line 10 | m('button', { 11 | onclick: function() { 12 | count++; 13 | }, 14 | }, count + ' clicks'), 15 | ]); 16 | }, 17 | }; 18 | } -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(false); 3 | const presets = [ 4 | [ 5 | '@babel/preset-env', 6 | { 7 | useBuiltIns: 'entry', 8 | corejs: { version: '3.3' } 9 | //debug: true 10 | } 11 | ] 12 | ]; 13 | 14 | let plugins = [ 15 | [ 16 | 'transform-react-jsx', 17 | { 18 | pragma: 'm' 19 | } 20 | ], 21 | '@babel/plugin-proposal-class-properties', 22 | '@babel/plugin-transform-runtime' 23 | ]; 24 | return { 25 | presets, 26 | plugins 27 | }; 28 | }; 29 | -------------------------------------------------------------------------------- /src/view/components/maintenance-layout/index.js: -------------------------------------------------------------------------------- 1 | import './styles.scss'; 2 | 3 | /** 4 | * A component that will show the maintenance banner. 5 | */ 6 | export default function() { 7 | return { 8 | view: () => { 9 | return m('.maintenance', m('article', [ 10 | m('h1', 'We\'ll be back soon!'), 11 | m('div',[ 12 | m('p', m.trust('Sorry for the inconvenience but we\'re performing some maintenance at the moment. If you need to you can always contact us, otherwise we\'ll be back online shortly!')), 13 | m('p', '- The Team'), 14 | ]), 15 | ])); 16 | }, 17 | }; 18 | } -------------------------------------------------------------------------------- /src/view/pages/landing-page.js: -------------------------------------------------------------------------------- 1 | import SampleComponent from '../components/sample-component'; 2 | 3 | export default function() { 4 | return { 5 | view() { 6 | /* return m('div', [ 7 | m('h2', 'Congratulations, you made it!'), 8 | m('p', 'You\'ve spun up your very first Mithril app :-)'), 9 | m(SampleComponent), 10 | ]); */ 11 | return ( 12 |
13 |

Congratulations, you made it!

14 |

You've spun up your very first Mithril app :-)

15 | 16 |
17 | ); 18 | }, 19 | }; 20 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import 'core-js/stable'; 2 | import 'regenerator-runtime/runtime'; 3 | 4 | /* Import all vendor.scss / css here[e.g. Import 'font-awesome/scss/font-awesome.scss';] */ 5 | import { Routes, DefaultRoute } from './view/routes'; 6 | 7 | /* Include global app styles here, so that it will over ride component's css styles*/ 8 | import './app.scss'; 9 | 10 | if (module.hot) { 11 | module.hot.accept(); 12 | } 13 | 14 | if (process.env.NODE_ENV !== 'production') { 15 | console.log('Looks like we are in development mode!'); 16 | } 17 | 18 | console.log('Env var test ===>', process.env.BASE_URL, process.env.BASE_URL_EXPAND); 19 | // Wire up mithril app to DOM 20 | const $root = document.body.querySelector('#root'); 21 | m.route($root, DefaultRoute, Routes); 22 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const webpackMerge = require('webpack-merge'); 2 | const common = require('./config/webpack/webpack.common'); 3 | /* eslint-disable global-require,import/no-dynamic-require */ 4 | const env = process.env.NODE_ENV || 'development'; 5 | const envConfig = require(`./config/webpack/webpack.${env}`); 6 | 7 | const getAddons = addonsArgs => { 8 | const addons = Array.isArray(addonsArgs) ? addonsArgs : [addonsArgs]; 9 | return addons 10 | .filter(Boolean) 11 | .map(name => require(`./config/webpack/addons/webpack.${name}.js`)); 12 | }; 13 | 14 | module.exports = ({ addon } = {}) => { 15 | console.log('process.env.NODE_ENV===>', env, 'addon ==>', addon); 16 | return webpackMerge(common, envConfig, ...getAddons(addon)); 17 | }; 18 | -------------------------------------------------------------------------------- /config/webpack/webpack.common.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const paths = require('./paths'); 4 | const rules = require('./rules'); 5 | 6 | module.exports = { 7 | context: paths.contextPath, 8 | entry: { 9 | main: paths.entryPath, 10 | }, 11 | module: { 12 | rules, 13 | }, 14 | resolve: { 15 | modules: ['src', 'node_modules'], 16 | extensions: ['*', '.js', '.scss', '.css'], 17 | }, 18 | plugins: [ 19 | new HtmlWebpackPlugin({ 20 | template: paths.templatePath, 21 | favicon: paths.faviconPath, 22 | }), 23 | new webpack.ProvidePlugin({ 24 | m: 'mithril', //Global access 25 | }), 26 | ], 27 | }; 28 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | commonjs: true, 5 | es6: true, 6 | node: true, 7 | }, 8 | globals: { 9 | 'm': true 10 | }, 11 | extends: [ 12 | 'eslint:recommended', 13 | 'plugin:mithril/recommended' 14 | ], 15 | parserOptions: { 16 | sourceType: 'module', 17 | ecmaVersion: 9, 18 | ecmaFeatures: { 19 | jsx: true 20 | } 21 | }, 22 | rules: { 23 | 'comma-dangle': ['error', 'always-multiline'], 24 | 'indent': ['error', 4], 25 | 'linebreak-style': ['error', 'unix'], 26 | 'quotes': ['error', 'single'], 27 | 'semi': ['error', 'always'], 28 | 'no-unused-vars': ['warn'], 29 | 'no-console': 0, 30 | 'rest-spread-spacing': ["error", "never"], 31 | }, 32 | }; -------------------------------------------------------------------------------- /config/webpack/paths.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | 3 | const contextPath = resolve(__dirname, '../', '../', 'src'); 4 | const outputPath = resolve(__dirname, '../', '../', 'dist'); 5 | const bundleReportPath = resolve(outputPath, 'report.html'); 6 | const entryPath = resolve(contextPath, 'index.js'); 7 | const templatePath = resolve(contextPath, 'index.html'); 8 | const envDevPath = resolve(__dirname, '../', 'environment', '.env.development'); 9 | const envProdPath = resolve(__dirname, '../', 'environment', '.env.production'); 10 | const envPath = resolve(__dirname, '../', 'environment', '.env'); 11 | const eslintConfigPath = resolve(__dirname, '../', '../', '.eslintrc.js'); 12 | const nodeModulesPath = resolve(__dirname, '../', '../', 'node_modules'); 13 | const faviconPath = resolve(contextPath, 'images', 'favicon.png'); 14 | 15 | module.exports = { 16 | contextPath, 17 | outputPath, 18 | bundleReportPath, 19 | entryPath, 20 | templatePath, 21 | envDevPath, 22 | envProdPath, 23 | envPath, 24 | eslintConfigPath, 25 | nodeModulesPath, 26 | faviconPath, 27 | }; 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Dhinesh Kumar 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. 22 | -------------------------------------------------------------------------------- /src/view/routes.js: -------------------------------------------------------------------------------- 1 | import PageLayout from './components/page-layout'; 2 | 3 | // Individual pages 4 | import IndexPage from './pages/landing-page'; 5 | import Splash from './components/splash-loader/index'; 6 | import MaintenancePage from './components/maintenance-layout/index'; 7 | 8 | function loadSpinner() { 9 | let $splashDiv = document.getElementById('splash'); 10 | if (!$splashDiv) { 11 | $splashDiv = document.createElement('div'); 12 | $splashDiv.setAttribute('id', 'splash'); 13 | const $root = document.body.querySelector('#root'); 14 | $root.appendChild($splashDiv); 15 | } 16 | m.render($splashDiv, m(Splash)); 17 | } 18 | function hideSpinner() { 19 | let $splashDiv = document.getElementById('splash'); 20 | if ($splashDiv) { 21 | m.render($splashDiv, null); 22 | } 23 | } 24 | 25 | const Routes = { 26 | '/splash': { 27 | render: function() { 28 | return m(PageLayout, m(Splash)); 29 | }, 30 | }, 31 | '/index': { 32 | onmatch() { 33 | // Show Loader until the promise has been resolved or rejected. 34 | loadSpinner(); 35 | return new Promise((resolve /*, reject*/) => { 36 | //Fetch all necessary data here 37 | setTimeout(function() { 38 | //m.render($root, null); 39 | resolve(); 40 | }, 2000); 41 | }).catch((/* e */) => { 42 | // In case of server error we can show the maintenance page. 43 | return MaintenancePage; 44 | }); 45 | }, 46 | render(vnode) { 47 | hideSpinner(); 48 | if (typeof vnode.tag === 'function') { 49 | //If onmatch returns a component or a promise that resolves to a component, comes here. 50 | return vnode; 51 | } 52 | return m(PageLayout, m(IndexPage)); 53 | }, 54 | }, 55 | }; 56 | 57 | const DefaultRoute = '/index'; 58 | 59 | export { Routes, DefaultRoute }; 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mithril-starter-kit", 3 | "version": "2.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "cross-env NODE_ENV=development webpack-dev-server --config webpack.config.js", 8 | "build": "cross-env NODE_ENV=production webpack --progress --config webpack.config.js", 9 | "build:analyze": "npm run build -- --env.addon=bundleanalyze --env.addon=bundlevisualizer", 10 | "lint:js": "eslint src/ webpack.*.js --cache --fix" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@babel/core": "^7.9.6", 17 | "@babel/plugin-proposal-class-properties": "^7.8.3", 18 | "@babel/plugin-transform-runtime": "^7.9.6", 19 | "@babel/preset-env": "^7.9.6", 20 | "babel-loader": "^8.1.0", 21 | "babel-plugin-transform-react-jsx": "^6.24.1", 22 | "clean-webpack-plugin": "^3.0.0", 23 | "core-js": "^3.6.5", 24 | "cross-env": "^7.0.2", 25 | "css-loader": "^3.5.3", 26 | "dotenv-webpack": "^1.7.0", 27 | "eslint": "^6.8.0", 28 | "eslint-loader": "^4.0.2", 29 | "eslint-plugin-mithril": "^0.1.1", 30 | "file-loader": "^6.0.0", 31 | "html-loader": "^1.1.0", 32 | "html-webpack-plugin": "^4.3.0", 33 | "mini-css-extract-plugin": "^0.9.0", 34 | "node-sass": "^7.0.0", 35 | "optimize-css-assets-webpack-plugin": "^5.0.3", 36 | "postcss-flexbugs-fixes": "^4.2.1", 37 | "postcss-loader": "^3.0.0", 38 | "postcss-normalize": "^9.0.0", 39 | "postcss-preset-env": "^6.7.0", 40 | "prettier": "^2.0.5", 41 | "sass-loader": "^8.0.2", 42 | "terser-webpack-plugin": "^3.0.0", 43 | "url-loader": "^4.1.0", 44 | "webpack": "^4.43.0", 45 | "webpack-bundle-analyzer": "^4.7.0", 46 | "webpack-cli": "^3.3.11", 47 | "webpack-dev-server": "^3.10.3", 48 | "webpack-merge": "^4.2.2", 49 | "webpack-visualizer-plugin": "^0.1.11" 50 | }, 51 | "dependencies": { 52 | "@babel/runtime": "^7.9.6", 53 | "mithril": "^2.0.4", 54 | "regenerator-runtime": "^0.13.5" 55 | }, 56 | "browserslist": [ 57 | "IE 10" 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /config/webpack/webpack.development.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 3 | const Dotenv = require('dotenv-webpack'); 4 | const paths = require('./paths'); 5 | 6 | module.exports = { 7 | mode: 'development', 8 | devtool: 'cheap-module-source-map', 9 | devServer: { 10 | hot: true, 11 | contentBase: paths.outputPath, 12 | port: 3000, 13 | proxy: { 14 | '/api': 'http://localhost:3001', 15 | }, 16 | open: true, 17 | /*overlay: { 18 | errors: true, 19 | warnings: true, 20 | },*/ 21 | compress: true, 22 | historyApiFallback: true, 23 | }, 24 | module: { 25 | rules: [ 26 | { 27 | test: /\.js$/, 28 | enforce: 'pre', 29 | exclude: /node_modules/, 30 | loader: 'eslint-loader', 31 | options: { 32 | cache: false, 33 | configFile: paths.eslintConfigPath, 34 | emitWarning: true, 35 | // Fail only on errors 36 | failOnWarning: false, 37 | failOnError: false, 38 | // Toggle autofix 39 | fix: false, 40 | formatter: require('eslint/lib/cli-engine/formatters/stylish'), 41 | }, 42 | }, 43 | ], 44 | }, 45 | output: { 46 | path: paths.outputPath, 47 | filename: 'js/[name].js', 48 | chunkFilename: 'js/[name].js', 49 | }, 50 | plugins: [ 51 | new Dotenv({ 52 | path: paths.envDevPath, // Path to .env.development file 53 | expand: true, 54 | }), 55 | new Dotenv({ 56 | path: paths.envPath, // Path to .env file 57 | expand: true, 58 | }), 59 | new MiniCssExtractPlugin({ 60 | filename: 'css/[name].css', 61 | chunkFilename: 'css/[id].css', 62 | }), 63 | new webpack.HotModuleReplacementPlugin(), 64 | ], 65 | optimization: { 66 | splitChunks: { 67 | chunks: 'all', 68 | }, 69 | }, 70 | }; 71 | -------------------------------------------------------------------------------- /src/view/components/splash-loader/styles.scss: -------------------------------------------------------------------------------- 1 | $count: 7; 2 | $time: 2; //in seconds 3 | $size: 100; 4 | $color: #ffffff; 5 | 6 | .holder { 7 | position: absolute; 8 | left: 0px; 9 | top: 0px; 10 | bottom: 0px; 11 | right: 0px; 12 | width: 100%; 13 | height: 100%; 14 | background-color: #2D2F48; 15 | 16 | .preloader { 17 | /* size */ 18 | width: $size + px; 19 | height: $size + px; 20 | position: absolute; 21 | left: 50%; 22 | top: 50%; 23 | transform: translateX(-50%) translateY(-50%); 24 | animation: rotatePreloader $time + s infinite ease-in; 25 | } 26 | @keyframes rotatePreloader { 27 | 0% { 28 | transform: translateX(-50%) translateY(-50%) rotateZ(0deg); 29 | } 30 | 100% { 31 | transform: translateX(-50%) translateY(-50%) rotateZ(-360deg); 32 | } 33 | } 34 | .preloader div { 35 | position: absolute; 36 | width: 100%; 37 | height: 100%; 38 | opacity: 0; 39 | } 40 | .preloader div:before { 41 | content: ""; 42 | position: absolute; 43 | left: 50%; 44 | top: 0%; 45 | width: 10%; 46 | height: 10%; 47 | background-color: $color; 48 | transform: translateX(-50%); 49 | border-radius: 50%; 50 | } 51 | 52 | @for $i from 1 through $count { 53 | .preloader div:nth-child(#{$i}) { 54 | transform: rotateZ(((360 / $count) * ($i - 1)) + deg); 55 | animation: rotateCircle + $i $time + s infinite linear; 56 | z-index: $count - $i; 57 | } 58 | @keyframes rotateCircle#{$i} { 59 | #{percentage(((50 / $count) * ($i - 1)) / 100)} { 60 | opacity: 0; 61 | } 62 | #{percentage((((50 / $count) + 0.0001) * ($i - 1)) / 100)} { 63 | opacity: 1; 64 | transform: rotateZ((0 - ((360 / $count) * ($i - 2))) + deg); 65 | } 66 | #{percentage((((50 / $count) * ($i - 0)) + 2) / 100)} { 67 | transform: rotateZ((0 - ((360 / $count) * ($i - 1))) + deg); 68 | } 69 | #{percentage(((50 + ((50 / $count) * ($i - 0))) + 2) / 100)} { 70 | transform: rotateZ((0 - ((360 / $count) * ($i - 1))) + deg); 71 | } 72 | 100% { 73 | transform: rotateZ((0 - ((360 / $count) * ($count - 1))) + deg); 74 | opacity: 1; 75 | } 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /config/webpack/webpack.production.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack'); 2 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 3 | const TerserPlugin = require('terser-webpack-plugin'); 4 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 5 | const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); 6 | const Dotenv = require('dotenv-webpack'); 7 | const paths = require('./paths'); 8 | 9 | module.exports = { 10 | mode: 'production', 11 | devtool: 'source-map', 12 | stats: { 13 | //https://github.com/webpack-contrib/mini-css-extract-plugin/issues/138 14 | children: false, 15 | assets: false, 16 | warnings: false, 17 | }, 18 | output: { 19 | path: paths.outputPath, 20 | filename: 'js/[name]-[contenthash:8].js', 21 | chunkFilename: 'js/[name]-[contenthash:8].js', 22 | }, 23 | plugins: [ 24 | new webpack.HashedModuleIdsPlugin(), // so that file hashes don't change unexpectedly 25 | new CleanWebpackPlugin(), 26 | new Dotenv({ 27 | path: paths.envProdPath, // Path to .env.production file 28 | expand: true, 29 | }), 30 | new Dotenv({ 31 | path: paths.envPath, // Path to .env file 32 | expand: true, 33 | }), 34 | new MiniCssExtractPlugin({ 35 | filename: 'css/[name]-[contenthash:8].css', 36 | chunkFilename: 'css/[name]-[contenthash:8].css', 37 | }), 38 | ], 39 | optimization: { 40 | runtimeChunk: 'single', 41 | splitChunks: { 42 | chunks: 'all', 43 | maxInitialRequests: Infinity, 44 | minSize: 30000, 45 | cacheGroups: { 46 | vendor: { 47 | test: /[\\/]node_modules[\\/]/, 48 | name(module) { 49 | // get the name. E.g. node_modules/packageName/not/this/part.js 50 | // or node_modules/packageName 51 | const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1]; 52 | 53 | // npm package names are URL-safe, but some servers don't like @ symbols 54 | return `vendor.${packageName.replace('@', '')}`; 55 | }, 56 | }, 57 | }, 58 | }, 59 | minimizer: [new TerserPlugin({}), new OptimizeCSSAssetsPlugin({})], 60 | }, 61 | }; 62 | -------------------------------------------------------------------------------- /config/webpack/rules.js: -------------------------------------------------------------------------------- 1 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); 2 | const postcssNormalize = require('postcss-normalize'); 3 | const imageInlineSizeLimit = 10000; 4 | const fontInlineSizeLimit = 10000; 5 | 6 | module.exports = [ 7 | { 8 | test: /\.(ttf|otf|eot|woff(2)?)(\?[a-z0-9]+)?$/, 9 | loader: 'url-loader', 10 | options: { 11 | limit: fontInlineSizeLimit, 12 | name: 'fonts/[name].[ext]', 13 | }, 14 | }, 15 | { 16 | test: /\.(sa|sc|c)ss$/, 17 | use: [ 18 | { 19 | loader: MiniCssExtractPlugin.loader, 20 | options: { 21 | hmr: process.env.NODE_ENV === 'development', 22 | // if hmr does not work, this is a forceful method. 23 | reloadAll: process.env.NODE_ENV === 'development', 24 | publicPath: '../', 25 | }, 26 | }, 27 | { 28 | loader: 'css-loader', 29 | }, 30 | { 31 | loader: 'postcss-loader', 32 | options: { 33 | ident: 'postcss', 34 | plugins: () => [ 35 | require('postcss-flexbugs-fixes'), 36 | require('postcss-preset-env')({ 37 | autoprefixer: { 38 | flexbox: 'no-2009', 39 | }, 40 | stage: 3, 41 | }), 42 | postcssNormalize(), 43 | ], 44 | }, 45 | }, 46 | { 47 | loader: 'sass-loader', 48 | }, 49 | ], 50 | }, 51 | { 52 | test: /\.(jpe?g|png|gif)$/, 53 | use: { 54 | loader: 'url-loader', 55 | options: { 56 | limit: imageInlineSizeLimit, 57 | name: 'images/[name].[contenthash:8].[ext]', 58 | }, 59 | }, 60 | }, 61 | { 62 | test: /\.js$/, 63 | exclude: /node_modules/, 64 | use: { 65 | loader: 'babel-loader', 66 | }, 67 | }, 68 | { 69 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 70 | loader: 'url-loader', 71 | options: { 72 | limit: imageInlineSizeLimit, 73 | name: 'images/[name].[contenthash:8].[ext]', 74 | }, 75 | }, 76 | ]; 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mithril Starter Kit 2 | A boilerplate Mithril application using ES6, Babel, Webpack 4, Sass/SCSS, Webpack dev server hot reload and eslint 3 | 4 | ### How to use 5 | By using [degit](https://github.com/Rich-Harris/degit) 6 | ```shell 7 | npx degit dhinesh03/mithril-starter-kit mithril-starter-kit 8 | or 9 | git clone https://github.com/dhinesh03/mithril-starter-kit 10 | ``` 11 | or 12 | [Download the source](https://github.com/dhinesh03/mithril-starter-kit/zipball/master) 13 | ```shell 14 | cd mithril-starter-kit 15 | 16 | npm install 17 | 18 | npm start 19 | # Webpack dev server will run and opens the app on the browser with HRM, 20 | npm run build 21 | # Compiles the app for production and all compiled files lies on dist dir. 22 | # To deploy an the application simply transfer the dist to a web server's public directory. 23 | npm run build:analyze 24 | # Compiles the app for production and we will have a report and stats for the bundle on dist folder. 25 | ``` 26 | 27 | ## Directory Structure: 28 | ```shell 29 | ├── dist/ # Compiled application 30 | │ ├── css/ 31 | │ │ ├── *.css 32 | │ │ └── *.css.map 33 | │ ├── fonts/ 34 | │ ├── images/ 35 | │ ├── js/ 36 | │ │ ├── *.js 37 | │ │ └── *.js.map 38 | │ └── index.html 39 | │ 40 | ├── src/ # Application source files 41 | │ │ 42 | │ ├── images/ # Image files that are copied to build production output (e.g. favicon.ico) 43 | │ │ 44 | │ ├── view/ # All your application view logic files 45 | │ │ │ 46 | │ │ ├── components/ # All your view components 47 | │ │ │ │ 48 | │ │ │ ├── your-component/ # A single view component 49 | │ │ │ │ ├── index.js # The view component code 50 | │ │ │ │ ├── styles.scss # The view component styles 51 | │ │ │ │ └── ... 52 | │ │ │ └── ... 53 | │ │ │ 54 | │ │ ├── pages/ # All your top level page components 55 | │ │ │ ├── your-pages.js 56 | │ │ │ └── ... 57 | │ │ │ 58 | │ │ └── routes.js # Application routing definition 59 | │ │ 60 | │ ├── index.html # Application HTML template 61 | │ └── index.js # Application entry point 62 | │ 63 | ├── eslintrc.js # ESLint configuration 64 | ├── package.json # NPM configuration and scripts 65 | ├── config/environment/env.* # Holding environment variables 66 | └── config/webpack.*.js # Webpack configuration 67 | ``` 68 | --------------------------------------------------------------------------------