├── .editorconfig ├── .eslintignore ├── .eslintrc.cjs ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .nvmrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── ava.config.js ├── bin └── pangolin-core.js ├── commands ├── build.js ├── dev.js ├── docs.js └── inspect.js ├── lib ├── copy-dir.js ├── create-fractal-instance.js ├── format-ip.js ├── generate-asset-filename.js ├── generate-output-filename.js ├── get-asset-files.js ├── get-config.js ├── get-dirname.js ├── get-host-ips.js ├── get-paths.js ├── get-pkg.js ├── get-port.js ├── is-data-type.js ├── merge-objects.js └── pangolin-head-extension.js ├── package-lock.json ├── package.json ├── stylelint.config.cjs ├── test ├── project │ ├── .browserslistrc │ ├── .gitignore │ ├── babel.config.js │ ├── package.json │ ├── pangolin.config.js │ ├── postcss.config.cjs │ ├── public │ │ ├── favicon.ico │ │ └── logo.svg │ └── src │ │ ├── components │ │ ├── _preview.njk │ │ ├── button │ │ │ ├── README.md │ │ │ ├── button.config.yml │ │ │ ├── button.js │ │ │ ├── button.njk │ │ │ └── button.scss │ │ ├── media │ │ │ ├── casey-horner-JxtcTiWIJXQ-unsplash.jpg │ │ │ ├── image.js │ │ │ ├── image.njk │ │ │ └── image.scss │ │ └── typography │ │ │ ├── headings.njk │ │ │ └── lists.njk │ │ ├── docs │ │ └── index.md │ │ ├── main.js │ │ ├── main.scss │ │ └── setup │ │ ├── JetBrainsMono-Regular.woff │ │ ├── JetBrainsMono-Regular.woff2 │ │ ├── fonts.scss │ │ ├── scaffolding.scss │ │ └── variables.scss └── unit │ ├── lib │ ├── copy-dir.spec.js │ ├── format-ip.spec.js │ ├── generate-asset-filename.spec.js │ ├── generate-output-filename.spec.js │ ├── get-asset-files.spec.js │ ├── get-config.spec.js │ ├── get-dirname.spec.js │ ├── get-host-ips.spec.js │ ├── get-path.spec.js │ ├── get-pkg.spec.js │ ├── get-port.spec.js │ ├── helpers │ │ ├── package.json │ │ └── pangolin.config.js │ ├── is-data-type.spec.js │ ├── merge-objects.spec.js │ ├── pangolin-head-extension.spec.js │ └── snapshots │ │ ├── copy-dir.spec.js.md │ │ ├── copy-dir.spec.js.snap │ │ ├── generate-asset-filename.spec.js.md │ │ ├── generate-asset-filename.spec.js.snap │ │ ├── get-asset-files.spec.js.md │ │ ├── get-asset-files.spec.js.snap │ │ ├── get-config.spec.js.md │ │ ├── get-config.spec.js.snap │ │ ├── get-path.spec.js.md │ │ ├── get-path.spec.js.snap │ │ ├── get-pkg.spec.js.md │ │ ├── get-pkg.spec.js.snap │ │ ├── merge-objects.spec.js.md │ │ ├── merge-objects.spec.js.snap │ │ ├── pangolin-head-extension.spec.js.md │ │ └── pangolin-head-extension.spec.js.snap │ └── setup │ └── date.js └── webpack ├── base.js ├── build.js └── dev.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EDITORCONFIG 2 | # http://editorconfig.org/ 3 | # ============================================================================== 4 | 5 | 6 | # Top-most EditorConfig file 7 | root = true 8 | 9 | 10 | # Global rules 11 | # ========================================================== 12 | 13 | [*] 14 | 15 | # Indentation 16 | indent_style = space 17 | indent_size = 2 18 | 19 | # Line endings and trailing whitespace 20 | end_of_line = lf 21 | trim_trailing_whitespace = true 22 | 23 | # Charset 24 | charset = utf-8 25 | 26 | # Final new line 27 | insert_final_newline = true 28 | 29 | 30 | # Markdown rules 31 | # ========================================================== 32 | 33 | [*.md] 34 | 35 | # Trailing whitespace 36 | trim_trailing_whitespace = false 37 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | test/project/dist 2 | test/project/public/assets 3 | test/project/static 4 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 4 | '@pangolinjs' 5 | ], 6 | rules: { 7 | 'no-console': 'off' 8 | }, 9 | overrides: [ 10 | { 11 | files: [ 12 | 'test/unit/**/*.spec.?(m)js' 13 | ], 14 | extends: [ 15 | 'plugin:ava/recommended' 16 | ] 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | on: [push] 3 | 4 | jobs: 5 | 6 | lint: 7 | name: Lint 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Check out repository 11 | uses: actions/checkout@v3 12 | - name: Set up Node.js 13 | uses: actions/setup-node@v3 14 | with: 15 | node-version: 18 16 | - name: Install dependencies 17 | run: npm ci 18 | - name: Lint CSS 19 | run: npm run lint:css 20 | - name: Lint JS 21 | run: npm run lint:js 22 | 23 | test: 24 | name: Test 25 | runs-on: ${{ matrix.os }} 26 | strategy: 27 | matrix: 28 | os: [ubuntu-latest, macos-latest] 29 | node: [16, 18] 30 | steps: 31 | - name: Check out repository 32 | uses: actions/checkout@v3 33 | - name: Set up Node.js 34 | uses: actions/setup-node@v3 35 | with: 36 | node-version: ${{ matrix.node }} 37 | - name: Install dependencies 38 | run: npm ci 39 | - name: Test unit 40 | run: npm run test:unit 41 | - name: Test build 42 | run: | 43 | cd test/project 44 | npm run build 45 | - name: Test inspect 46 | run: | 47 | cd test/project 48 | npm run inspect -- build 49 | npm run inspect -- dev 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | node_modules 3 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [6.1.2](https://github.com/pangolinjs/core/compare/v6.1.1...v6.1.2) (2022-05-04) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * Add file protocol to dynamic import ([#109](https://github.com/pangolinjs/core/issues/109)) ([c5313ef](https://github.com/pangolinjs/core/commit/c5313ef7d5294d1a00def157b5943d622f1719c0)) 11 | * Make host IP extractor Node 18 compatible ([db66632](https://github.com/pangolinjs/core/commit/db66632ce308a2333c556686ca30ce407ac98d13)) 12 | 13 | ### [6.1.1](https://github.com/pangolinjs/core/compare/v6.1.0...v6.1.1) (2021-08-24) 14 | 15 | 16 | ### Bug Fixes 17 | 18 | * Create dist folder before copying files ([309c405](https://github.com/pangolinjs/core/commit/309c405eb6a69cf76ed80ceaa05a6497ef91ccfc)) 19 | 20 | ## [6.1.0](https://github.com/pangolinjs/core/compare/v6.0.1...v6.1.0) (2021-08-09) 21 | 22 | 23 | ### Features 24 | 25 | * Migrate to webpack Asset Modules ([7f89a3b](https://github.com/pangolinjs/core/commit/7f89a3b1d7ffc07c331681be7ce96f55387dedcc)) 26 | 27 | ### [6.0.1](https://github.com/pangolinjs/core/compare/v6.0.0...v6.0.1) (2021-06-29) 28 | 29 | 30 | ### Bug Fixes 31 | 32 | * Add extensions to webpack resolver ([4b31c93](https://github.com/pangolinjs/core/commit/4b31c93d1b987071c4baf56e28096ba1f6d0dead)) 33 | 34 | ## [6.0.0](https://github.com/pangolinjs/core/compare/v6.0.0-rc.9...v6.0.0) (2021-06-13) 35 | 36 | ## [6.0.0-rc.9](https://github.com/pangolinjs/core/compare/v6.0.0-rc.8...v6.0.0-rc.9) (2021-04-07) 37 | 38 | 39 | ### Features 40 | 41 | * Set more specific labels for component HTML tabs ([af50c6a](https://github.com/pangolinjs/core/commit/af50c6adc286ff96eb1996aceebcf0c9ab04eaba)) 42 | 43 | ## [6.0.0-rc.8](https://github.com/pangolinjs/core/compare/v6.0.0-rc.7...v6.0.0-rc.8) (2021-02-19) 44 | 45 | 46 | ### Bug Fixes 47 | 48 | * Specify correct bin file ([7686b10](https://github.com/pangolinjs/core/commit/7686b10f9ae60ba13c6207fd409ac7b677712d53)) 49 | 50 | ## [6.0.0-rc.7](https://github.com/pangolinjs/core/compare/v6.0.0-rc.6...v6.0.0-rc.7) (2021-02-19) 51 | 52 | ## [6.0.0-rc.6](https://github.com/pangolinjs/core/compare/v6.0.0-rc.5...v6.0.0-rc.6) (2021-02-19) 53 | 54 | ## [6.0.0-rc.5](https://github.com/pangolinjs/core/compare/v6.0.0-rc.4...v6.0.0-rc.5) (2021-01-14) 55 | 56 | 57 | ### Bug Fixes 58 | 59 | * Generated paths in docs head ([eddace4](https://github.com/pangolinjs/core/commit/eddace40f201ddece79ad37d49390ebd04d61ebc)) 60 | 61 | ## [6.0.0-rc.4](https://github.com/pangolinjs/core/compare/v6.0.0-rc.3...v6.0.0-rc.4) (2021-01-14) 62 | 63 | 64 | ### Features 65 | 66 | * Split build into separate build and docs commands ([25d3bd9](https://github.com/pangolinjs/core/commit/25d3bd98d73ba9920994525b4f0c7b138aea7755)) 67 | * Use absolute URLs for assets linked from CSS or JS ([9bab164](https://github.com/pangolinjs/core/commit/9bab164f6ccd3aba46b3782d07aa7c8c52cf507e)) 68 | 69 | ## [6.0.0-rc.3](https://github.com/pangolinjs/core/compare/v6.0.0-rc.2...v6.0.0-rc.3) (2021-01-04) 70 | 71 | ## [6.0.0-rc.2](https://github.com/pangolinjs/core/compare/v6.0.0-rc.1...v6.0.0-rc.2) (2020-12-08) 72 | 73 | 74 | ### Bug Fixes 75 | 76 | * Manifest plugin import ([f6438e7](https://github.com/pangolinjs/core/commit/f6438e721202677105c4087a4fbadbb70311f376)) 77 | 78 | ## [6.0.0-rc.1](https://github.com/pangolinjs/core/compare/v6.0.0-rc.0...v6.0.0-rc.1) (2020-12-08) 79 | 80 | 81 | ### Features 82 | 83 | * Allow UI label customization ([d77f28b](https://github.com/pangolinjs/core/commit/d77f28bfd2517c98d0c8c51468c23801e4a73f98)) 84 | 85 | 86 | ### Bug Fixes 87 | 88 | * Increment port for Fractal ([d3506ab](https://github.com/pangolinjs/core/commit/d3506ab0c561749eb5416452189cd312a8c79641)) 89 | 90 | ## [6.0.0-rc.0](https://github.com/pangolinjs/core/compare/v6.0.0-beta.4...v6.0.0-rc.0) (2020-11-04) 91 | 92 | 93 | ### Features 94 | 95 | * Use custom sidebar information config (Close [#97](https://github.com/pangolinjs/core/issues/97)) ([cd3bfb2](https://github.com/pangolinjs/core/commit/cd3bfb25a59206f759abe7b33b8e5e918650cbb4)) 96 | 97 | 98 | ### Bug Fixes 99 | 100 | * Gracefully shut down dev server ([62ee1ae](https://github.com/pangolinjs/core/commit/62ee1ae5ec98a15a3626239e2186c270cd611edb)) 101 | 102 | ## [6.0.0-beta.4](https://github.com/pangolinjs/core/compare/v6.0.0-beta.3...v6.0.0-beta.4) (2020-10-21) 103 | 104 | 105 | ### Bug Fixes 106 | 107 | * Apply correct number of loaders before css-loader ([adb453f](https://github.com/pangolinjs/core/commit/adb453fabfe554d243537d49007e07c0bace0343)) 108 | * Temporary solution for broken HMR ([381419d](https://github.com/pangolinjs/core/commit/381419d99de1e0abf4679f9e97762f756de6fb0a)) 109 | 110 | ## [6.0.0-beta.3](https://github.com/pangolinjs/core/compare/v6.0.0-beta.2...v6.0.0-beta.3) (2020-10-20) 111 | 112 | ## [6.0.0-beta.2](https://github.com/pangolinjs/core/compare/v6.0.0-beta.1...v6.0.0-beta.2) (2020-10-20) 113 | 114 | ## [6.0.0-beta.1](https://github.com/pangolinjs/core/compare/v6.0.0-beta.0...v6.0.0-beta.1) (2020-09-18) 115 | 116 | 117 | ### Bug Fixes 118 | 119 | * Set correct public asset path for Fractal ([ea990ae](https://github.com/pangolinjs/core/commit/ea990ae8d8a9dbf447b250a262356bf3e974b88f)) 120 | 121 | ## [6.0.0-beta.0](https://github.com/pangolinjs/core/compare/v6.0.0-alpha.5...v6.0.0-beta.0) (2020-09-18) 122 | 123 | 124 | ### Features 125 | 126 | * Expose engine configuration ([1eaf009](https://github.com/pangolinjs/core/commit/1eaf009c027eb0fcf6d5bc9d0f06fdaf90ced60d)) 127 | * Expose webpack configuration ([48872dd](https://github.com/pangolinjs/core/commit/48872dd90e2cfc987df256b933e1838a6d4c2486)) 128 | * Serve UI and files from a single port ([dfd040e](https://github.com/pangolinjs/core/commit/dfd040e992dcee6b498e7e62fc0d0f324d6301a1)) 129 | 130 | ## [6.0.0-alpha.5](https://github.com/pangolinjs/core/compare/v6.0.0-alpha.4...v6.0.0-alpha.5) (2020-09-09) 131 | 132 | 133 | ### Bug Fixes 134 | 135 | * Switch to new postcss-loader options format ([77df2a7](https://github.com/pangolinjs/core/commit/77df2a76361213ecdebd220c3d82687579af023c)) 136 | 137 | ## [6.0.0-alpha.4](https://github.com/pangolinjs/core/compare/v6.0.0-alpha.3...v6.0.0-alpha.4) (2020-09-09) 138 | 139 | 140 | ### Features 141 | 142 | * Resolve CSS URLs relative to file paths ([c1338cf](https://github.com/pangolinjs/core/commit/c1338cfbd49fd3a6c703aff0fccd85ae6677fdc6)) 143 | 144 | 145 | ### Bug Fixes 146 | 147 | * Scope Pangolin's Nunjucks head tag ([8d66559](https://github.com/pangolinjs/core/commit/8d665592b0dc7549489c6d8e2664380711fbbfb7)) 148 | 149 | ## [6.0.0-alpha.3](https://github.com/pangolinjs/core/compare/v6.0.0-alpha.2...v6.0.0-alpha.3) (2020-09-08) 150 | 151 | 152 | ### Features 153 | 154 | * Generate relative assets paths ([3941979](https://github.com/pangolinjs/core/commit/394197936795a022c5f55d314c8886a630ea913d)) 155 | * Introduce separate "static" command ([1e9ced4](https://github.com/pangolinjs/core/commit/1e9ced4b43476a3a126073cf1cbfc30c25213f6c)) 156 | * Separate "static" command isn't necessary with relative URLs ([de7f405](https://github.com/pangolinjs/core/commit/de7f4052f250cea01018fc5554ce06e3bc95c88b)) 157 | 158 | ## [6.0.0-alpha.2](https://github.com/pangolinjs/core/compare/v6.0.0-alpha.1...v6.0.0-alpha.2) (2020-09-07) 159 | 160 | ## [6.0.0-alpha.1](https://github.com/pangolinjs/core/compare/v6.0.0-alpha.0...v6.0.0-alpha.1) (2020-09-07) 161 | 162 | 163 | ### Features 164 | 165 | * Implement file name hashing ([a32d8cb](https://github.com/pangolinjs/core/commit/a32d8cb9a6906f23d921fc93754e64c3dfa5985e)) 166 | 167 | ## [6.0.0-alpha.0](https://github.com/pangolinjs/core/compare/v5.8.2...v6.0.0-alpha.0) (2020-08-11) 168 | 169 | 170 | ### Features 171 | 172 | * Setup v6 ([5207092](https://github.com/pangolinjs/core/commit/5207092a5526dcc1b40e6b9b3057309d7ff60a22)) 173 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | HIPPOCRATIC LICENSE 2 | 3 | Version 3.0, October 2021 4 | 5 | TERMS AND CONDITIONS 6 | 7 | TERMS AND CONDITIONS FOR USE, COPY, MODIFICATION, PREPARATION OF DERIVATIVE WORK, REPRODUCTION, AND DISTRIBUTION: 8 | 9 | 10 | * DEFINITIONS: 11 | 12 | 13 | This section defines certain terms used throughout this license agreement. 14 | 15 | 1.1. “License” means the terms and conditions, as stated herein, for use, copy, modification, preparation of derivative work, reproduction, and distribution of Software (as defined below). 16 | 17 | 1.2. “Licensor” means the copyright and/or patent owner or entity authorized by the copyright and/or patent owner that is granting the License. 18 | 19 | 1.3. “Licensee” means the individual or entity exercising permissions granted by this License, including the use, copy, modification, preparation of derivative work, reproduction, and distribution of Software (as defined below). 20 | 21 | 1.4. “Software” means any copyrighted work, including but not limited to software code, authored by Licensor and made available under this License. 22 | 23 | 1.5. “Supply Chain” means the sequence of processes involved in the production and/or distribution of a commodity, good, or service offered by the Licensee. 24 | 25 | 1.6. “Supply Chain Impacted Party” or “Supply Chain Impacted Parties” means any person(s) directly impacted by any of Licensee’s Supply Chain, including the practices of all persons or entities within the Supply Chain prior to a good or service reaching the Licensee. 26 | 27 | 1.7. “Duty of Care” is defined by its use in tort law, delict law, and/or similar bodies of law closely related to tort and/or delict law, including without limitation, a requirement to act with the watchfulness, attention, caution, and prudence that a reasonable person in the same or similar circumstances would use towards any Supply Chain Impacted Party. 28 | 29 | 1.8. “Worker” is defined to include any and all permanent, temporary, and agency workers, as well as piece-rate, salaried, hourly paid, legal young (minors), part-time, night, and migrant workers. 30 | 31 | 32 | * INTELLECTUAL PROPERTY GRANTS: 33 | 34 | 35 | This section identifies intellectual property rights granted to a Licensee. 36 | 37 | 2.1. Grant of Copyright License: Subject to the terms and conditions of this License, Licensor hereby grants to Licensee a worldwide, non-exclusive, no-charge, royalty-free copyright license to use, copy, modify, prepare derivative work, reproduce, or distribute the Software, Licensor authored modified software, or other work derived from the Software. 38 | 39 | 2.2 Grant of Patent License: Subject to the terms and conditions of this License, Licensor hereby grants Licensee a worldwide, non-exclusive, no-charge, royalty-free patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer Software. 40 | 41 | 42 | * ETHICAL STANDARDS: 43 | 44 | 45 | This section lists conditions the Licensee must comply with in order to have rights under this License. 46 | 47 | The rights granted to the Licensee by this License are expressly made subject to the Licensee’s ongoing compliance with the following conditions: 48 | 49 | 3.1. The Licensee SHALL NOT, whether directly or indirectly, through agents or assigns: 50 | 51 | 3.1.1. Infringe upon any person's right to life or security of person, engage in extrajudicial killings, or commit murder, without lawful cause 52 | (See Article 3, *United Nations Universal Declaration of Human Rights*; Article 6, *International Covenant on Civil and Political Rights*) 53 | 54 | 3.1.2. Hold any person in slavery, servitude, or forced labor 55 | (See Article 4, *United Nations Universal Declaration of Human Rights*; Article 8, *International Covenant on Civil and Political Rights*); 56 | 57 | 3.1.3. Contribute to the institution of slavery, slave trading, forced labor, or unlawful child labor 58 | (See Article 4, *United Nations Universal Declaration of Human Rights*; Article 8, *International Covenant on Civil and Political Rights*); 59 | 60 | 3.1.4. Torture or subject any person to cruel, inhumane, or degrading treatment or punishment 61 | (See Article 5, *United Nations Universal Declaration of Human Rights*; Article 7, *International Covenant on Civil and Political Rights*); 62 | 63 | 3.1.5. Discriminate on the basis of sex, gender, sexual orientation, race, ethnicity, nationality, religion, caste, age, medical disability or impairment, and/or any other like circumstances 64 | (See Article 7, *United Nations Universal Declaration of Human Rights*; Article 2, *International Covenant on Economic, Social and Cultural Rights*; Article 26, *International Covenant on Civil and Political Rights*); 65 | 66 | 3.1.6. Prevent any person from exercising his/her/their right to seek an effective remedy by a competent court or national tribunal (including domestic judicial systems, international courts, arbitration bodies, and other adjudicating bodies) for actions violating the fundamental rights granted to him/her/them by applicable constitutions, applicable laws, or by this License 67 | (See Article 8, *United Nations Universal Declaration of Human Rights*; Articles 9 and 14, *International Covenant on Civil and Political Rights*); 68 | 69 | 3.1.7. Subject any person to arbitrary arrest, detention, or exile 70 | (See Article 9, *United Nations Universal Declaration of Human Rights*; Article 9, *International Covenant on Civil and Political Rights*); 71 | 72 | 3.1.8. Subject any person to arbitrary interference with a person's privacy, family, home, or correspondence without the express written consent of the person 73 | (See Article 12, *United Nations Universal Declaration of Human Rights*; Article 17, *International Covenant on Civil and Political Rights*); 74 | 75 | 3.1.9. Arbitrarily deprive any person of his/her/their property 76 | (See Article 17, *United Nations Universal Declaration of Human Rights*); 77 | 78 | 3.1.10. Forcibly remove indigenous peoples from their lands or territories or take any action with the aim or effect of dispossessing indigenous peoples from their lands, territories, or resources, including without limitation the intellectual property or traditional knowledge of indigenous peoples, without the free, prior, and informed consent of indigenous peoples concerned 79 | (See Articles 8 and 10, *United Nations Declaration on the Rights of Indigenous Peoples*); 80 | 81 | 3.1.11. (Module -- Carbon Underground 200) Be an individual or entity, or a representative, agent, affiliate, successor, attorney, or assign of an individual or entity, on the FFI Solutions Carbon Underground 200 list; 82 | 83 | 3.1.12. (Module -- Ecocide) Commit ecocide: 84 | 85 | 3.1.12.1 For the purpose of this section, "ecocide" means unlawful or wanton acts committed with knowledge that there is a substantial likelihood of severe and either widespread or long-term damage to the environment being caused by those acts; 86 | 87 | 3.1.12.2 For the purpose of further defining ecocide and the terms contained in the previous paragraph: 88 | 89 | 3.1.12.2.1. "Wanton" means with reckless disregard for damage which would be clearly excessive in relation to the social and economic benefits anticipated; 90 | 91 | 3.1.12.2.2. "Severe" means damage which involves very serious adverse changes, disruption, or harm to any element of the environment, including grave impacts on human life or natural, cultural, or economic resources; 92 | 93 | 3.1.12.2.3. "Widespread" means damage which extends beyond a limited geographic area, crosses state boundaries, or is suffered by an entire ecosystem or species or a large number of human beings; 94 | 95 | 3.1.12.2.4. "Long-term" means damage which is irreversible or which cannot be redressed through natural recovery within a reasonable period of time; and 96 | 97 | 3.1.12.2.5. "Environment" means the earth, its biosphere, cryosphere, lithosphere, hydrosphere, and atmosphere, as well as outer space 98 | 99 | (See Section II, *Independent Expert Panel for the Legal Definition of Ecocide*, Stop Ecocide Foundation and the Promise Institute for Human Rights at UCLA School of Law, June 2021); 100 | 101 | 3.1.13. (Module -- Extractive Industries) Be an individual or entity, or a representative, agent, affiliate, successor, attorney, or assign of an individual or entity, that engages in fossil fuel or mineral exploration, extraction, development, or sale; 102 | 103 | 3.1.14. (Module -- BDS) Be an individual or entity, or a representative, agent, affiliate, successor, attorney, or assign of an individual or entity, identified by the Boycott, Divestment, Sanctions ("BDS") movement on its website ([https://bdsmovement.net/](https://bdsmovement.net/) and [https://bdsmovement.net/get-involved/what-to-boycott](https://bdsmovement.net/get-involved/what-to-boycott)) as a target for boycott; 104 | 105 | 3.1.15. (Module -- Taliban) Be an individual or entity that: 106 | 107 | 3.1.15.1. engages in any commercial transactions with the Taliban; or 108 | 109 | 3.1.15.2. is a representative, agent, affiliate, successor, attorney, or assign of the Taliban; 110 | 111 | 3.1.16. (Module -- Myanmar) Be an individual or entity that: 112 | 113 | 3.1.16.1. engages in any commercial transactions with the Myanmar/Burmese military junta; or 114 | 115 | 3.1.16.2. is a representative, agent, affiliate, successor, attorney, or assign of the Myanmar/Burmese government; 116 | 117 | 3.1.17. (Module -- Xinjiang Uygur Autonomous Region) Be an individual or entity, or a representative, agent, affiliate, successor, attorney, or assign of any individual or entity, that does business in, purchases goods from, or otherwise benefits from goods produced in the Xinjiang Uygur Autonomous Region of China; 118 | 119 | 3.1.18. (Module -- U.S. Tariff Act) Be an individual or entity: 120 | 121 | 3.1.18.1. which U.S. Customs and Border Protection (CBP) has currently issued a Withhold Release Order (WRO) or finding against based on reasonable suspicion of forced labor; or 122 | 123 | 3.1.18.2. that is a representative, agent, affiliate, successor, attorney, or assign of an individual or entity that does business with an individual or entity which currently has a WRO or finding from CBP issued against it based on reasonable suspicion of forced labor; 124 | 125 | 3.1.19. (Module -- Mass Surveillance) Be a government agency or multinational corporation, or a representative, agent, affiliate, successor, attorney, or assign of a government or multinational corporation, which participates in mass surveillance programs; 126 | 127 | 3.1.20. (Module -- Military Activities) Be an entity or a representative, agent, affiliate, successor, attorney, or assign of an entity which conducts military activities; 128 | 129 | 3.1.21. (Module -- Law Enforcement) Be an individual or entity, or a or a representative, agent, affiliate, successor, attorney, or assign of an individual or entity, that provides good or services to, or otherwise enters into any commercial contracts with, any local, state, or federal law enforcement agency; 130 | 131 | 3.1.22. (Module -- Media) Be an individual or entity, or a or a representative, agent, affiliate, successor, attorney, or assign of an individual or entity, that broadcasts messages promoting killing, torture, or other forms of extreme violence; 132 | 133 | 3.1.23. Interfere with Workers' free exercise of the right to organize and associate 134 | (See Article 20, United Nations Universal Declaration of Human Rights; C087 - Freedom of Association and Protection of the Right to Organise Convention, 1948 (No. 87), International Labour Organization; Article 8, International Covenant on Economic, Social and Cultural Rights); and 135 | 136 | 3.1.24. Harm the environment in a manner inconsistent with local, state, national, or international law. 137 | 138 | 139 | 3.2. The Licensee SHALL: 140 | 141 | 3.2.1. (Module -- Social Auditing) Only use social auditing mechanisms that adhere to Worker-Driven Social Responsibility Network's Statement of Principles (https://wsr-network.org/what-is-wsr/statement-of-principles/) over traditional social auditing mechanisms, to the extent the Licensee uses any social auditing mechanisms at all; 142 | 143 | 3.2.2. (Module -- Workers on Board of Directors) Ensure that if the Licensee has a Board of Directors, 30% of Licensee's board seats are held by Workers paid no more than 200% of the compensation of the lowest paid Worker of the Licensee; 144 | 145 | 3.2.3. (Module -- Supply Chain Transparency) Provide clear, accessible supply chain data to the public in accordance with the following conditions: 146 | 147 | 3.2.3.1. All data will be on Licensee's website and/or, to the extent Licensee is a representative, agent, affiliate, successor, attorney, subsidiary, or assign, on Licensee's principal's or parent's website or some other online platform accessible to the public via an internet search on a common internet search engine; and 148 | 149 | 3.2.3.2. Data published will include, where applicable, manufacturers, top tier suppliers, subcontractors, cooperatives, component parts producers, and farms; 150 | 151 | 3.2.4. Provide equal pay for equal work where the performance of such work requires equal skill, effort, and responsibility, and which are performed under similar working conditions, except where such payment is made pursuant to: 152 | 153 | 3.2.4.1. A seniority system; 154 | 155 | 3.2.4.2. A merit system; 156 | 157 | 3.2.4.3. A system which measures earnings by quantity or quality of production; or 158 | 159 | 3.2.4.4. A differential based on any other factor other than sex, gender, sexual orientation, race, ethnicity, nationality, religion, caste, age, medical disability or impairment, and/or any other like circumstances 160 | (See 29 U.S.C.A. � 206(d)(1); Article 23, *United Nations Universal Declaration of Human Rights*; Article 7, *International Covenant on Economic, Social and Cultural Rights*; Article 26, *International Covenant on Civil and Political Rights*); and 161 | 162 | 3.2.5. Allow for reasonable limitation of working hours and periodic holidays with pay 163 | (See Article 24, *United Nations Universal Declaration of Human Rights*; Article 7, *International Covenant on Economic, Social and Cultural Rights*). 164 | 165 | 166 | 167 | * SUPPLY CHAIN IMPACTED PARTIES: 168 | 169 | 170 | This section identifies additional individuals or entities that a Licensee could harm as a result of violating the Ethical Standards section, the condition that the Licensee must voluntarily accept a Duty of Care for those individuals or entities, and the right to a private right of action that those individuals or entities possess as a result of violations of the Ethical Standards section. 171 | 172 | 4.1. In addition to the above Ethical Standards, Licensee voluntarily accepts a Duty of Care for Supply Chain Impacted Parties of this License, including individuals and communities impacted by violations of the Ethical Standards. The Duty of Care is breached when a provision within the Ethical Standards section is violated by a Licensee, one of its successors or assigns, or by an individual or entity that exists within the Supply Chain prior to a good or service reaching the Licensee. 173 | 174 | 4.2. Breaches of the Duty of Care, as stated within this section, shall create a private right of action, allowing any Supply Chain Impacted Party harmed by the Licensee to take legal action against the Licensee in accordance with applicable negligence laws, whether they be in tort law, delict law, and/or similar bodies of law closely related to tort and/or delict law, regardless if Licensee is directly responsible for the harms suffered by a Supply Chain Impacted Party. Nothing in this section shall be interpreted to include acts committed by individuals outside of the scope of his/her/their employment. 175 | 176 | 177 | 178 | * NOTICE: 179 | This section explains when a Licensee must notify others of the License. 180 | 181 | 182 | 5.1. Distribution of Notice: Licensee must ensure that everyone who receives a copy of or uses any part of Software from Licensee, with or without changes, also receives the License and the copyright notice included with Software (and if included by the Licensor, patent, trademark, and attribution notice). Licensee must ensure that License is prominently displayed so that any individual or entity seeking to download, copy, use, or otherwise receive any part of Software from Licensee is notified of this License and its terms and conditions. Licensee must cause any modified versions of the Software to carry prominent notices stating that Licensee changed the Software. 183 | 184 | 5.2. Modified Software: Licensee is free to create modifications of the Software and distribute only the modified portion created by Licensee, however, any derivative work stemming from the Software or its code must be distributed pursuant to this License, including this Notice provision. 185 | 186 | 5.3. Recipients as Licensees: Any individual or entity that uses, copies, modifies, reproduces, distributes, or prepares derivative work based upon the Software, all or part of the Software’s code, or a derivative work developed by using the Software, including a portion of its code, is a Licensee as defined above and is subject to the terms and conditions of this License. 187 | 188 | 189 | * REPRESENTATIONS AND WARRANTIES: 190 | 191 | 192 | 6.1. Disclaimer of Warranty: TO THE FULL EXTENT ALLOWED BY LAW, THIS SOFTWARE COMES “AS IS,” WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED, AND LICENSOR SHALL NOT BE LIABLE TO ANY PERSON OR ENTITY FOR ANY DAMAGES OR OTHER LIABILITY ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THIS LICENSE, UNDER ANY LEGAL CLAIM. 193 | 194 | 6.2. Limitation of Liability: LICENSEE SHALL HOLD LICENSOR HARMLESS AGAINST ANY AND ALL CLAIMS, DEBTS, DUES, LIABILITIES, LIENS, CAUSES OF ACTION, DEMANDS, OBLIGATIONS, DISPUTES, DAMAGES, LOSSES, EXPENSES, ATTORNEYS’ FEES, COSTS, LIABILITIES, AND ALL OTHER CLAIMS OF EVERY KIND AND NATURE WHATSOEVER, WHETHER KNOWN OR UNKNOWN, ANTICIPATED OR UNANTICIPATED, FORESEEN OR UNFORESEEN, ACCRUED OR UNACCRUED, DISCLOSED OR UNDISCLOSED, ARISING OUT OF OR RELATING TO LICENSEE’S USE OF THE SOFTWARE. NOTHING IN THIS SECTION SHOULD BE INTERPRETED TO REQUIRE LICENSEE TO INDEMNIFY LICENSOR, NOR REQUIRE LICENSOR TO INDEMNIFY LICENSEE. 195 | 196 | 197 | * TERMINATION 198 | 199 | 200 | 7.1. Violations of Ethical Standards or Breaching Duty of Care: If Licensee violates the Ethical Standards section or Licensee, or any other person or entity within the Supply Chain prior to a good or service reaching the Licensee, breaches its Duty of Care to Supply Chain Impacted Parties, Licensee must remedy the violation or harm caused by Licensee within 30 days of being notified of the violation or harm. If Licensee fails to remedy the violation or harm within 30 days, all rights in the Software granted to Licensee by License will be null and void as between Licensor and Licensee. 201 | 202 | 7.2. Failure of Notice: If any person or entity notifies Licensee in writing that Licensee has not complied with the Notice section of this License, Licensee can keep this License by taking all practical steps to comply within 30 days after the notice of noncompliance. If Licensee does not do so, Licensee’s License (and all rights licensed hereunder) will end immediately. 203 | 204 | 7.3. Judicial Findings: In the event Licensee is found by a civil, criminal, administrative, or other court of competent jurisdiction, or some other adjudicating body with legal authority, to have committed actions which are in violation of the Ethical Standards or Supply Chain Impacted Party sections of this License, all rights granted to Licensee by this License will terminate immediately. 205 | 206 | 7.4. Patent Litigation: If Licensee institutes patent litigation against any entity (including a cross-claim or counterclaim in a suit) alleging that the Software, all or part of the Software’s code, or a derivative work developed using the Software, including a portion of its code, constitutes direct or contributory patent infringement, then any patent license, along with all other rights, granted to Licensee under this License will terminate as of the date such litigation is filed. 207 | 208 | 7.5. Additional Remedies: Termination of the License by failing to remedy harms in no way prevents Licensor or Supply Chain Impacted Party from seeking appropriate remedies at law or in equity. 209 | 210 | 211 | * MISCELLANEOUS: 212 | 213 | 214 | 8.1. Conditions: Sections 3, 4.1, 5.1, 5.2, 7.1, 7.2, 7.3, and 7.4 are conditions of the rights granted to Licensee in the License. 215 | 216 | 8.2. Equitable Relief: Licensor and any Supply Chain Impacted Party shall be entitled to equitable relief, including injunctive relief or specific performance of the terms hereof, in addition to any other remedy to which they are entitled at law or in equity. 217 | 218 | 8.3. (Module – Copyleft) Copyleft: Modified software, source code, or other derivative work must be licensed, in its entirety, under the exact same conditions as this License. 219 | 220 | 8.4. Severability: If any term or provision of this License is determined to be invalid, illegal, or unenforceable by a court of competent jurisdiction, any such determination of invalidity, illegality, or unenforceability shall not affect any other term or provision of this License or invalidate or render unenforceable such term or provision in any other jurisdiction. If the determination of invalidity, illegality, or unenforceability by a court of competent jurisdiction pertains to the terms or provisions contained in the Ethical Standards section of this License, all rights in the Software granted to Licensee shall be deemed null and void as between Licensor and Licensee. 221 | 222 | 8.5. Section Titles: Section titles are solely written for organizational purposes and should not be used to interpret the language within each section. 223 | 224 | 8.6. Citations: Citations are solely written to provide context for the source of the provisions in the Ethical Standards. 225 | 226 | 8.7. Section Summaries: Some sections have a brief italicized description which is provided for the sole purpose of briefly describing the section and should not be used to interpret the terms of the License. 227 | 228 | 8.8. Entire License: This is the entire License between the Licensor and Licensee with respect to the claims released herein and that the consideration stated herein is the only consideration or compensation to be paid or exchanged between them for this License. This License cannot be modified or amended except in a writing signed by Licensor and Licensee. 229 | 230 | 8.9. Successors and Assigns: This License shall be binding upon and inure to the benefit of the Licensor’s and Licensee’s respective heirs, successors, and assigns. 231 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pangolin.js Core 2 | 3 | Framework for design system development with Nunjucks, Sass, and JavaScript. 4 | 5 | Requires [Node.js 15 or higher](https://nodejs.org). 6 | 7 | ## Create a new project with `npx` 8 | 9 | ```bash 10 | npx @pangolinjs/cli create project-name 11 | ``` 12 | 13 | ## Usage 14 | 15 | ### Start dev server 16 | 17 | ```bash 18 | npm run dev 19 | ``` 20 | 21 | ### Build files for production 22 | 23 | ```bash 24 | npm run build 25 | ``` 26 | 27 | ### Export for static file hosting 28 | 29 | ```bash 30 | npm run docs 31 | ``` 32 | 33 | ### Lint CSS and JavaScript 34 | 35 | ```bash 36 | npm run lint:css 37 | npm run lint:js 38 | ``` 39 | 40 | For more information take a look at the [Pangolin.js docs](https://pangolinjs.org). 41 | 42 | ## Contribute 43 | 44 | ```bash 45 | # Build UI 46 | npm run prepare 47 | 48 | # Testing 49 | npm run test:unit 50 | 51 | # Linting 52 | npm run lint:css 53 | npm run lint:js 54 | ``` 55 | 56 | ## License 57 | 58 | [Hippocratic License 2.1](https://firstdonoharm.dev) 59 | -------------------------------------------------------------------------------- /ava.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | verbose: true, 3 | files: [ 4 | 'test/unit/**/*.spec.*' 5 | ], 6 | require: [ 7 | './test/unit/setup/date.js' 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /bin/pangolin-core.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { green } from 'kleur/colors' 4 | 5 | import build from '../commands/build.js' 6 | import dev from '../commands/dev.js' 7 | import docs from '../commands/docs.js' 8 | import inspect from '../commands/inspect.js' 9 | 10 | const command = process.argv[2] 11 | const context = process.cwd() 12 | 13 | switch (command) { 14 | case 'dev': 15 | dev({ context }) 16 | break 17 | case 'build': 18 | build({ context }) 19 | break 20 | case 'docs': 21 | docs({ context }) 22 | break 23 | case 'inspect': 24 | inspect({ context, command: process.argv[3] }) 25 | break 26 | default: 27 | console.log('Please use one of the following commands:') 28 | console.log(' - dev') 29 | console.log(' - build') 30 | console.log(' - docs') 31 | console.log(' - inspect (dev|build)') 32 | console.log(`\nFor example: ${green('pangolin-core dev')}`) 33 | } 34 | -------------------------------------------------------------------------------- /commands/build.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises' 2 | import webpack from 'webpack' 3 | 4 | import copyDir from '../lib/copy-dir.js' 5 | import getConfig from '../lib/get-config.js' 6 | import getPaths from '../lib/get-paths.js' 7 | import getWebpackConfig from '../webpack/build.js' 8 | 9 | /** 10 | * Build production assets 11 | * @param {Object} options Options 12 | * @param {string} options.context Working directory 13 | */ 14 | export default async function ({ context }, callback) { 15 | process.env.NODE_ENV = 'production' 16 | 17 | const config = await getConfig({ context }) 18 | const paths = getPaths({ context }) 19 | const webpackConfig = await getWebpackConfig({ context }) 20 | 21 | if (typeof config.webpack === 'function') { 22 | config.webpack(webpackConfig) 23 | } 24 | 25 | const webpackCompiler = webpack(webpackConfig.toConfig()) 26 | 27 | await fs.rm(paths.outputAssets, { recursive: true, force: true }) 28 | await fs.rm(paths.outputBuild, { recursive: true, force: true }) 29 | 30 | webpackCompiler.run(async (error, stats) => { 31 | if (error) { 32 | console.error(error) 33 | } 34 | 35 | console.log(stats.toString({ 36 | children: false, 37 | chunks: false, 38 | colors: true, 39 | modules: false 40 | })) 41 | 42 | if (stats.hasErrors()) { 43 | // Exit with a non-zero status code to allow CI tools to report errors. 44 | process.exit(1) 45 | } 46 | 47 | await copyDir(paths.inputPublic, paths.outputBuild) 48 | 49 | callback?.(error, { stats, webpackConfig }) 50 | }) 51 | } 52 | -------------------------------------------------------------------------------- /commands/dev.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises' 2 | import webpack from 'webpack' 3 | import WebpackDevServer from 'webpack-dev-server' 4 | 5 | import createFractalInstance from '../lib/create-fractal-instance.js' 6 | import getConfig from '../lib/get-config.js' 7 | import getPaths from '../lib/get-paths.js' 8 | import getPort from '../lib/get-port.js' 9 | import getWebpackConfig from '../webpack/dev.js' 10 | 11 | /** 12 | * Run development server 13 | * @param {Object} options Options 14 | * @param {string} options.context Working directorys 15 | */ 16 | export default async function ({ context }) { 17 | process.env.NODE_ENV = 'development' 18 | 19 | const config = await getConfig({ context }) 20 | const paths = getPaths({ context }) 21 | const host = '0.0.0.0' 22 | const webpackPort = await getPort(8080) 23 | const fractalPort = await getPort(webpackPort + 1) 24 | const webpackConfig = await getWebpackConfig({ context, host, port: webpackPort }) 25 | 26 | if (typeof config.webpack === 'function') { 27 | config.webpack(webpackConfig) 28 | } 29 | 30 | const publicPath = webpackConfig.output.get('publicPath') 31 | 32 | const webpackServerOptions = { 33 | static: [ 34 | { 35 | publicPath, 36 | watch: false 37 | } 38 | ], 39 | client: { 40 | logging: 'none' 41 | }, 42 | proxy: { 43 | [`!${publicPath}**`]: `http://${host}:${fractalPort}` 44 | } 45 | } 46 | 47 | const webpackCompiler = webpack(webpackConfig.toConfig()) 48 | const webpackServer = new WebpackDevServer(webpackServerOptions, webpackCompiler) 49 | 50 | await fs.rm(paths.outputAssets, { recursive: true, force: true }) 51 | await webpackServer.start(webpackPort, host) 52 | 53 | const fractalServerOptions = { 54 | sync: true, 55 | port: fractalPort, 56 | syncOptions: { 57 | ui: false, 58 | ghostMode: false, 59 | watchOptions: { 60 | // webpack-dev-server already includes reloading, so we ignore 61 | // everything except Fractal-related files. 62 | ignored: file => !/\.(njk|yml|json|md)$/.test(file) 63 | } 64 | } 65 | } 66 | 67 | const fractalInstance = await createFractalInstance({ context, publicPath }) 68 | const fractalServer = fractalInstance.web.server(fractalServerOptions) 69 | 70 | fractalServer.on('error', error => { 71 | fractalInstance.cli.console.error(error.message) 72 | }) 73 | 74 | fractalServer.on('stopped', async () => { 75 | await webpackServer.stop() 76 | 77 | // Exit the whole application after the webpack-dev-server has been closed. 78 | process.exit() 79 | }) 80 | 81 | process.on('SIGINT', () => { 82 | // webpack-dev-server will be closed after the Fractal server has been stopped. 83 | // See above listener for the Fractal 'stopped' event. 84 | fractalServer.stop() 85 | }) 86 | 87 | fractalServer.start() 88 | } 89 | -------------------------------------------------------------------------------- /commands/docs.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises' 2 | 3 | import build from './build.js' 4 | import createFractalInstance from '../lib/create-fractal-instance.js' 5 | import getAssetFiles from '../lib/get-asset-files.js' 6 | import getPaths from '../lib/get-paths.js' 7 | 8 | /** 9 | * Create docs for static hosting 10 | * @param {Object} options Options 11 | * @param {string} options.context Working directory 12 | */ 13 | export default async function ({ context }) { 14 | process.env.NODE_ENV = 'production' 15 | 16 | const paths = getPaths({ context }) 17 | 18 | build({ context }, async (_, { stats, webpackConfig }) => { 19 | const publicPath = webpackConfig.output.get('publicPath') 20 | const assets = getAssetFiles({ files: Object.keys(stats.compilation.assets) }) 21 | 22 | const fractalInstance = await createFractalInstance({ context, publicPath, assets }) 23 | const fractalConsole = fractalInstance.cli.console 24 | const fractalBuilder = fractalInstance.web.builder() 25 | 26 | await fs.rm(paths.outputStatic, { recursive: true, force: true }) 27 | 28 | fractalBuilder.on('progress', (completed, total) => { 29 | fractalConsole.update(`Fractal: Exported ${completed} of ${total} items.`, 'info') 30 | }) 31 | 32 | fractalBuilder.on('error', error => { 33 | fractalConsole.error(error.message) 34 | }) 35 | 36 | fractalBuilder.build().then(stats => { 37 | if (stats.errorCount) { 38 | fractalConsole.error('Fractal build failed.') 39 | process.exit(1) 40 | } 41 | 42 | fractalConsole.success('Fractal build completed.') 43 | }) 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /commands/inspect.js: -------------------------------------------------------------------------------- 1 | import { green } from 'kleur/colors' 2 | import util from 'util' 3 | 4 | import webpackBuild from '../webpack/build.js' 5 | import webpackDev from '../webpack/dev.js' 6 | import getConfig from '../lib/get-config.js' 7 | 8 | /** 9 | * Print webpack configuration 10 | * @param {Object} options Options 11 | * @param {string} options.context Working directory 12 | * @param {'dev'|'build'} options.command Command to inspect 13 | */ 14 | export default async function ({ context, command }) { 15 | const config = await getConfig({ context }) 16 | 17 | let webpackOptions 18 | 19 | switch (command) { 20 | case 'dev': 21 | process.env.NODE_ENV = 'development' 22 | webpackOptions = webpackDev 23 | break 24 | case 'build': 25 | process.env.NODE_ENV = 'production' 26 | webpackOptions = webpackBuild 27 | break 28 | default: 29 | console.log('Please specify one of the following arguments:') 30 | console.log(' - dev') 31 | console.log(' - build') 32 | console.log(`\nFor example: ${green('npm run inspect -- dev')}`) 33 | return 34 | } 35 | 36 | webpackOptions = await webpackOptions({ context }) 37 | 38 | if (typeof config.webpack === 'function') { 39 | config.webpack(webpackOptions) 40 | } 41 | 42 | webpackOptions = util.inspect(webpackOptions.toConfig(), { 43 | colors: true, 44 | depth: null 45 | }) 46 | 47 | console.log(webpackOptions) 48 | } 49 | -------------------------------------------------------------------------------- /lib/copy-dir.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises' 2 | import path from 'path' 3 | 4 | /** 5 | * Copy directory (similar to `cp -r`) 6 | * @param {string} source Source path 7 | * @param {string} destination Destination path 8 | */ 9 | export default async function copyDir (source, destination) { 10 | try { 11 | await fs.access(destination) 12 | } catch { 13 | await fs.mkdir(destination, { recursive: true }) 14 | } 15 | 16 | const files = await fs.readdir(source) 17 | 18 | for (const file of files) { 19 | const filePath = path.join(source, file) 20 | const fileDest = path.join(destination, file) 21 | const fileStats = await fs.lstat(filePath) 22 | 23 | if (fileStats.isDirectory()) { 24 | await copyDir(filePath, fileDest) 25 | } else { 26 | await fs.copyFile(filePath, fileDest) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/create-fractal-instance.js: -------------------------------------------------------------------------------- 1 | import fractal from '@frctl/fractal' 2 | import mandelbrot from '@frctl/mandelbrot' 3 | import nunjucks from '@frctl/nunjucks' 4 | 5 | import getConfig from './get-config.js' 6 | import getPaths from './get-paths.js' 7 | import mergeObjects from './merge-objects.js' 8 | import PangolinHeadExtension from './pangolin-head-extension.js' 9 | 10 | /** 11 | * Create Fractal instance 12 | * @param {Object} options Options 13 | * @param {string} options.context Working directory 14 | * @param {string} options.publicPath Path to assets 15 | * @param {{js:string[], css:string[]}} options.assets Asset filenames 16 | */ 17 | export default async function ({ context, publicPath, assets }) { 18 | const paths = getPaths({ context }) 19 | const config = await getConfig({ context }) 20 | const instance = fractal.create() 21 | 22 | const theme = mandelbrot({ 23 | skin: config.ui.color, 24 | favicon: config.ui.favicon, 25 | format: config.ui.format, 26 | lang: config.ui.lang, 27 | information: config.ui.information, 28 | labels: config.ui.labels, 29 | panels: ['notes', 'info', 'html', 'view', 'context'] 30 | }) 31 | 32 | const engineOptions = mergeObjects(config.engine, { 33 | extensions: { 34 | PangolinHeadExtension: new PangolinHeadExtension({ publicPath, assets }) 35 | } 36 | }) 37 | 38 | const engine = nunjucks(engineOptions) 39 | 40 | instance.set('project.title', config.project.name) 41 | instance.set('project.author', config.project.author) 42 | instance.set('project.version', config.project.version) 43 | 44 | instance.components.engine(engine) 45 | instance.components.set('ext', '.njk') 46 | instance.components.set('path', paths.inputComponents) 47 | 48 | instance.docs.engine(engine) 49 | instance.docs.set('path', paths.inputDocs) 50 | 51 | instance.web.theme(theme) 52 | instance.web.set('static.path', paths.inputPublic) 53 | instance.web.set('builder.dest', paths.outputStatic) 54 | 55 | return instance 56 | } 57 | -------------------------------------------------------------------------------- /lib/format-ip.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Format IP 3 | * 4 | * Convert `0.0.0.0` and `::` to localhost. 5 | * @param {string} ip IP address 6 | * @returns {string} Formatted IP 7 | */ 8 | export default function (ip) { 9 | if (ip === '0.0.0.0' || ip === '::') { 10 | return 'localhost' 11 | } 12 | 13 | return ip 14 | } 15 | -------------------------------------------------------------------------------- /lib/generate-asset-filename.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate file-loader options 3 | * @param {Object} options Options 4 | * @param {'img'|'video'|'audio'|'font'} options.type File type 5 | * @param {boolean|'imported'|'all'} [options.hash] Hash file names 6 | * @returns {string} file-loader options 7 | */ 8 | export default function ({ type, hash }) { 9 | return `${type}/${hash ? '[contenthash:8]' : '[name]'}[ext]` 10 | } 11 | -------------------------------------------------------------------------------- /lib/generate-output-filename.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate output filename 3 | * @param {Object} options Options 4 | * @param {'js'|'css'} options.type File type 5 | * @param {boolean|'imported'|'all'} [options.hash] Hash file names 6 | * @returns {string} File name 7 | */ 8 | export default function ({ type, hash }) { 9 | return `${type}/${hash === 'all' ? '[contenthash:8]' : '[name]'}.${type}` 10 | } 11 | -------------------------------------------------------------------------------- /lib/get-asset-files.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Generate asset links 3 | * @param {Object} options Options 4 | * @param {string[]} options.files All emitted assets 5 | * @returns {{js:string[], css:string[]}} Asset links 6 | */ 7 | export default function ({ files }) { 8 | const js = files.filter(file => file.endsWith('.js')) 9 | const css = files.filter(file => file.endsWith('.css')) 10 | 11 | return { js, css } 12 | } 13 | -------------------------------------------------------------------------------- /lib/get-config.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | import getPkg from './get-pkg.js' 4 | import mergeObjects from './merge-objects.js' 5 | 6 | const defaultConfig = { 7 | project: { 8 | name: 'Pangolin.js', 9 | author: null, 10 | version: null, 11 | base: '/assets/' 12 | }, 13 | 14 | ui: { 15 | color: { 16 | accent: '#ff721f', 17 | complement: '#000', 18 | links: '#7f390f' 19 | }, 20 | favicon: '/favicon.ico', 21 | format: 'json', 22 | lang: 'en', 23 | information: [ 24 | { 25 | label: 'Version', 26 | value: null 27 | }, 28 | { 29 | label: 'Built on', 30 | value: new Date(), 31 | type: 'time', 32 | format: value => value.toISOString().slice(0, -8).replace('T', ' ') 33 | } 34 | ], 35 | labels: { 36 | panels: { 37 | html: 'Render', 38 | view: 'Source' 39 | } 40 | } 41 | }, 42 | 43 | hashFiles: 'imported' 44 | } 45 | 46 | /** 47 | * Get config 48 | * @param {Object} options Options 49 | * @param {string} [options.context] Working directory 50 | * @returns {Promise} Config 51 | */ 52 | export default async function ({ context }) { 53 | const pkg = await getPkg({ context }) 54 | 55 | if (pkg.name) { 56 | defaultConfig.project.name = pkg.name 57 | } 58 | 59 | if (pkg.version) { 60 | defaultConfig.project.version = pkg.version 61 | defaultConfig.ui.information[0].value = pkg.version 62 | } 63 | 64 | if (pkg.author) { 65 | defaultConfig.project.author = typeof pkg.author === 'string' 66 | ? pkg.author 67 | : pkg.author.name 68 | } 69 | 70 | try { 71 | const userConfigPath = 'file://' + path.join(context, 'pangolin.config.js') 72 | const { default: userConfig } = await import(userConfigPath) 73 | return mergeObjects(defaultConfig, userConfig) 74 | } catch { 75 | return defaultConfig 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /lib/get-dirname.js: -------------------------------------------------------------------------------- 1 | import { dirname } from 'path' 2 | import { fileURLToPath } from 'url' 3 | 4 | /** 5 | * Extract dirname from esm meta URL 6 | * @param {string} importMetaURL import.meta.url 7 | * @returns {string} Dirname 8 | */ 9 | export default function (importMetaURL) { 10 | return dirname(fileURLToPath(importMetaURL)) 11 | } 12 | -------------------------------------------------------------------------------- /lib/get-host-ips.js: -------------------------------------------------------------------------------- 1 | import { networkInterfaces } from 'os' 2 | 3 | /** 4 | * Check if network interface address is usable. 5 | * @param {import('os').NetworkInterfaceInfo} address 6 | * @returns {boolean} 7 | */ 8 | function isUsableAddress (address) { 9 | if (address.internal) { 10 | return false 11 | } 12 | 13 | if (address.family === 'IPv4') { 14 | return true 15 | } 16 | 17 | if (address.family === 4) { 18 | return true 19 | } 20 | } 21 | 22 | /** 23 | * Get a list of all IPv4 IPs associated with the host 24 | * @returns {string[]} List of IPv4 IPs 25 | */ 26 | export default function () { 27 | return Object.values(networkInterfaces()) 28 | .flat() 29 | .filter(isUsableAddress) 30 | .map(address => address.address) 31 | } 32 | -------------------------------------------------------------------------------- /lib/get-paths.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | 3 | /** 4 | * Get paths 5 | * @param {Object} options Options 6 | * @param {string} options.context Working directory 7 | */ 8 | export default function ({ context }) { 9 | return { 10 | inputComponents: path.join(context, 'src', 'components'), 11 | inputDocs: path.join(context, 'src', 'docs'), 12 | inputPublic: path.join(context, 'public'), 13 | outputAssets: path.join(context, 'public', 'assets'), 14 | outputBuild: path.join(context, 'dist'), 15 | outputStatic: path.join(context, 'static') 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/get-pkg.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises' 2 | import path from 'path' 3 | 4 | /** 5 | * Get package.json 6 | * @param {Object} options Options 7 | * @param {string} options.context Working directory 8 | * @returns {Promise} package.json 9 | */ 10 | export default async function ({ context }) { 11 | try { 12 | const file = await fs.readFile(path.join(context, 'package.json')) 13 | return JSON.parse(file) 14 | } catch { 15 | return {} 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/get-port.js: -------------------------------------------------------------------------------- 1 | import { createServer } from 'net' 2 | 3 | /** 4 | * Search for available port 5 | * Source: {@link https://gist.github.com/mikeal/1840641} 6 | * @param {number} port Desired port 7 | * @param {Function} callback Called with available port 8 | */ 9 | function getPort (port, callback) { 10 | const server = createServer() 11 | 12 | server.on('error', () => { 13 | getPort(++port, callback) 14 | }) 15 | 16 | // Explicitly listen on 0.0.0.0 to detect ports used by Docker 17 | // See: https://github.com/nodejs/node/issues/14034 18 | server.listen(port, '0.0.0.0', () => { 19 | server.once('close', () => { 20 | callback(port) 21 | }) 22 | server.close() 23 | }) 24 | } 25 | 26 | /** 27 | * Search for an available port 28 | * @param {number} port Desired port 29 | * @returns {Promise} Available port 30 | */ 31 | export default function (port) { 32 | return new Promise(resolve => { 33 | getPort(port, resolve) 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /lib/is-data-type.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Determine data type in a reliable way 3 | * 4 | * Screw you `typeof`! 5 | * @param {any} data The data 6 | * @param {any} type The type 7 | * @returns {Boolean} Data is of type 8 | */ 9 | export default function (data, type) { 10 | return Object.prototype.toString.call(data).slice(8, -1) === type.name 11 | } 12 | -------------------------------------------------------------------------------- /lib/merge-objects.js: -------------------------------------------------------------------------------- 1 | import isDataType from './is-data-type.js' 2 | 3 | /** 4 | * Deep merge two objects 5 | * @param {Object} target Target object 6 | * @param {Object} source Source object 7 | * @returns {Object} Merged objects 8 | */ 9 | export default function mergeObjects (target, source) { 10 | // Work on a shallow copy of the target so the original one won't be altered. 11 | const targetCopy = { ...target } 12 | 13 | for (const [key, value] of Object.entries(source)) { 14 | if (isDataType(value, Object)) { 15 | // Merge any source object into the corresponding target object, 16 | // or create a new one in the target in the key isn't present. 17 | targetCopy[key] = mergeObjects(targetCopy[key] ?? {}, source[key]) 18 | } else { 19 | targetCopy[key] = source[key] 20 | } 21 | } 22 | 23 | return targetCopy 24 | } 25 | -------------------------------------------------------------------------------- /lib/pangolin-head-extension.js: -------------------------------------------------------------------------------- 1 | import nunjucks from 'nunjucks' 2 | 3 | /** 4 | * Pangolin extension 5 | * @param {Object} options Options 6 | * @param {string} options.publicPath Path to assets 7 | * @param {{js:string[], css:string[]}} options.assets Asset files 8 | */ 9 | export default function ({ publicPath, assets }) { 10 | this.tags = ['pangolin_head'] 11 | 12 | this.parse = function (parser, nodes) { 13 | const token = parser.nextToken() 14 | parser.advanceAfterBlockEnd(token.value) 15 | return new nodes.CallExtension(this, 'run') 16 | } 17 | 18 | this.run = function () { 19 | if (process.env.NODE_ENV === 'development') { 20 | return new nunjucks.runtime.SafeString(``) 21 | } 22 | 23 | const js = assets.js.map(file => { 24 | return `` 25 | }) 26 | 27 | const css = assets.css.map(file => { 28 | return `` 29 | }) 30 | 31 | return new nunjucks.runtime.SafeString(js.join('\n') + '\n' + css.join('\n')) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pangolinjs/core", 3 | "version": "6.1.2", 4 | "description": "Framework for componentized front end development with Nunjucks, Sass, and JavaScript", 5 | "license": "Hippocratic-3.0", 6 | "author": { 7 | "name": "Fynn Becker", 8 | "email": "post@fynn.be" 9 | }, 10 | "homepage": "https://pangolinjs.org", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/pangolinjs/core" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/pangolinjs/core/issues", 17 | "email": "post@fynn.be" 18 | }, 19 | "files": [ 20 | "bin", 21 | "commands", 22 | "lib", 23 | "theme", 24 | "webpack" 25 | ], 26 | "type": "module", 27 | "engines": { 28 | "node": ">=15", 29 | "npm": ">=7" 30 | }, 31 | "scripts": { 32 | "lint:js": "eslint \"**/*.js\"", 33 | "lint:css": "stylelint **/*.scss", 34 | "test:unit": "c8 --reporter text --reporter html --all ava", 35 | "release": "standard-version --sign" 36 | }, 37 | "bin": { 38 | "pangolin-core": "bin/pangolin-core.js" 39 | }, 40 | "dependencies": { 41 | "@frctl/fractal": "^1.5.13", 42 | "@frctl/mandelbrot": "^1.10.1", 43 | "@frctl/nunjucks": "^2.0.14", 44 | "@nuxt/friendly-errors-webpack-plugin": "^2.5.2", 45 | "babel-loader": "^8.2.5", 46 | "css-loader": "^6.7.1", 47 | "cssnano": "^5.1.7", 48 | "kleur": "^4.1.4", 49 | "mini-css-extract-plugin": "^2.6.0", 50 | "postcss-loader": "^6.2.1", 51 | "resolve-url-loader": "^5.0.0", 52 | "sass-loader": "^12.6.0", 53 | "style-loader": "^3.3.1", 54 | "webpack": "^5.72.0", 55 | "webpack-bundle-analyzer": "^4.5.0", 56 | "webpack-chain": "^6.5.1", 57 | "webpack-dev-server": "^4.8.1", 58 | "webpack-manifest-plugin": "^5.0.0", 59 | "webpackbar": "^5.0.2" 60 | }, 61 | "peerDependencies": { 62 | "@babel/core": "^7.17.0", 63 | "postcss": "^8.3.6", 64 | "sass": "^1.38.1" 65 | }, 66 | "devDependencies": { 67 | "@babel/preset-env": "^7.17.10", 68 | "@pangolinjs/eslint-config": "^6.0.0", 69 | "@pangolinjs/stylelint-config": "^3.0.1", 70 | "autoprefixer": "^10.4.7", 71 | "ava": "^4.2.0", 72 | "c8": "^7.11.2", 73 | "core-js": "^3.22.4", 74 | "eslint": "^8.14.0", 75 | "eslint-config-standard": "^17.0.0", 76 | "eslint-plugin-ava": "^13.2.0", 77 | "eslint-plugin-import": "^2.26.0", 78 | "eslint-plugin-n": "^15.2.0", 79 | "eslint-plugin-promise": "^6.0.0", 80 | "standard-version": "^9.3.2", 81 | "stylelint": "^14.8.1", 82 | "stylelint-order": "^5.0.0", 83 | "stylelint-scss": "^4.2.0" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /stylelint.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | '@pangolinjs/stylelint-config' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /test/project/.browserslistrc: -------------------------------------------------------------------------------- 1 | last 2 Chrome versions 2 | last 2 Firefox versions 3 | last 2 Safari versions 4 | -------------------------------------------------------------------------------- /test/project/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | public/assets 3 | static 4 | -------------------------------------------------------------------------------- /test/project/babel.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | presets: [ 3 | ['@babel/preset-env', { 4 | useBuiltIns: 'usage', 5 | corejs: 3, 6 | bugfixes: true 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "author": { 4 | "name": "Fynn Becker" 5 | }, 6 | "type": "module", 7 | "scripts": { 8 | "dev": "node ../../bin/pangolin-core.js dev", 9 | "build": "node ../../bin/pangolin-core.js build", 10 | "docs": "node ../../bin/pangolin-core.js docs", 11 | "inspect": "node ../../bin/pangolin-core.js inspect" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/project/pangolin.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | project: { 3 | name: 'Hello World' 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/project/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['autoprefixer'] 3 | } 4 | -------------------------------------------------------------------------------- /test/project/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolinjs/core/e4d157cc1a476e845c4e87a1f452831e119b0289/test/project/public/favicon.ico -------------------------------------------------------------------------------- /test/project/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/project/src/components/_preview.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ _target.title }} | {{ _config.project.title }} 8 | 9 | {% pangolin_head %} 10 | 11 | 12 | {{ yield | safe }} 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/project/src/components/button/README.md: -------------------------------------------------------------------------------- 1 | Alias tempore temporibus expedita debitis quos autem aut earum. Id et excepturi quia quidem vel sed natus. Magni cum sequi aliquid fuga harum voluptas error. Et omnis culpa atque nulla. Culpa rerum a quam. 2 | 3 | * Libero accusantium quia sit et beatae impedit. 4 | * Maiores corrupti sit sit facilis. 5 | * Occaecati omnis atque nemo pariatur modi. 6 | * Qui nostrum ea qui fugit non rerum laudantium voluptatem. 7 | * Omnis eum consequatur et quos sequi. 8 | 9 | Aspernatur debitis fugiat ratione ex repellendus. Similique inventore aut animi reiciendis quae itaque. Odio molestiae tempora quo architecto voluptatem perspiciatis nam ducimus. 10 | 11 | Est vitae cupiditate fugiat. Porro fugiat voluptas sint facere minus. Esse eum maiores ipsa asperiores. Dicta aperiam et voluptas suscipit. Vero dolorem fugiat dolorem sequi est. Corporis alias aut nesciunt dolores quo quis incidunt. 12 | 13 | ```css 14 | .button { 15 | padding: 0.75em 1em; 16 | 17 | font-size: 1em; 18 | color: darkgreen; 19 | 20 | background: lightgreen; 21 | border: 0; 22 | } 23 | ``` 24 | 25 | Possimus reprehenderit exercitationem magnam laboriosam. Quo et facere error quisquam. Quasi facilis hic recusandae molestias quaerat. In quia ratione ut est assumenda nesciunt dolorem ut. 26 | -------------------------------------------------------------------------------- /test/project/src/components/button/button.config.yml: -------------------------------------------------------------------------------- 1 | context: 2 | text: "Hello World" 3 | size: "default" 4 | 5 | variants: 6 | - name: "small" 7 | status: "wip" 8 | context: 9 | size: "small" 10 | - name: "large" 11 | status: "prototype" 12 | context: 13 | size: "large" 14 | -------------------------------------------------------------------------------- /test/project/src/components/button/button.js: -------------------------------------------------------------------------------- 1 | const buttons = document.querySelectorAll('.js-button') 2 | 3 | for (const button of buttons) { 4 | button.addEventListener('click', () => { 5 | console.log('Hello World') 6 | }) 7 | } 8 | -------------------------------------------------------------------------------- /test/project/src/components/button/button.njk: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /test/project/src/components/button/button.scss: -------------------------------------------------------------------------------- 1 | .button { 2 | padding: 0.75em 1em; 3 | 4 | font-family: inherit; 5 | font-size: 1em; 6 | color: var(--darkgreen); 7 | 8 | background: var(--lightgreen); 9 | border: 0; 10 | 11 | &:hover, 12 | &:focus { 13 | color: var(--lightgreen); 14 | background: var(--darkgreen); 15 | } 16 | } 17 | 18 | .button--size-small { 19 | font-size: 0.8em; 20 | } 21 | 22 | .button--size-large { 23 | font-size: 1.5em; 24 | } 25 | -------------------------------------------------------------------------------- /test/project/src/components/media/casey-horner-JxtcTiWIJXQ-unsplash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolinjs/core/e4d157cc1a476e845c4e87a1f452831e119b0289/test/project/src/components/media/casey-horner-JxtcTiWIJXQ-unsplash.jpg -------------------------------------------------------------------------------- /test/project/src/components/media/image.js: -------------------------------------------------------------------------------- 1 | import file from './casey-horner-JxtcTiWIJXQ-unsplash.jpg' 2 | 3 | /** @type {HTMLImageElement} */ 4 | const image = document.querySelector('.js-media-image') 5 | 6 | if (image) { 7 | image.src = file 8 | } 9 | -------------------------------------------------------------------------------- /test/project/src/components/media/image.njk: -------------------------------------------------------------------------------- 1 |
2 | 3 | Pangolin.js logo 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/project/src/components/media/image.scss: -------------------------------------------------------------------------------- 1 | .media-image { 2 | width: 30em; 3 | height: 20em; 4 | 5 | background: 6 | url("casey-horner-JxtcTiWIJXQ-unsplash.jpg") 7 | center / cover 8 | no-repeat; 9 | } 10 | -------------------------------------------------------------------------------- /test/project/src/components/typography/headings.njk: -------------------------------------------------------------------------------- 1 |

Hello World

2 |

Lorem ipsum

3 |

Dolor sit amet

4 | -------------------------------------------------------------------------------- /test/project/src/components/typography/lists.njk: -------------------------------------------------------------------------------- 1 |
    2 |
  • Lorem ipsum dolor sit amet consectetur adipisicing elit. Nemo, nisi?
  • 3 |
  • Nam iure obcaecati sint quia distinctio et earum possimus vero!
  • 4 |
  • Doloribus sed ipsum provident perspiciatis id ipsa nam quisquam rerum.
  • 5 |
  • Repudiandae non labore excepturi ratione dicta. Eaque corrupti minus alias.
  • 6 |
  • Aspernatur officiis iusto eos cumque perferendis sapiente, distinctio aliquid pariatur?
  • 7 |
8 | 9 |
    10 |
  1. Lorem ipsum dolor sit amet consectetur adipisicing elit. Harum, vero!
  2. 11 |
  3. Odio, vel? Facere consectetur quibusdam numquam adipisci, enim ipsum laudantium.
  4. 12 |
  5. Exercitationem sunt beatae alias illum atque hic? Suscipit, nulla consequatur.
  6. 13 |
  7. Praesentium beatae fuga labore dolorem voluptas iure aut nisi cumque!
  8. 14 |
  9. Aliquid, quidem molestiae. Expedita eveniet consequuntur nesciunt distinctio commodi quam.
  10. 15 |
16 | 17 |
18 |
19 |
Lorem, ipsum dolor.
20 |
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Optio sed incidunt dolorum fugit alias nesciunt architecto aliquid pariatur beatae consequatur.
21 |
22 |
23 |
Ex, eum veniam.
24 |
Sapiente est deleniti ducimus sequi aspernatur. Magni, iste asperiores ducimus accusantium labore inventore optio, nulla consequuntur repellat sed ipsa. Expedita?
25 |
26 |
27 |
Ipsam, laudantium sint.
28 |
Facere dolor minima incidunt quo mollitia beatae praesentium hic similique, nam odit excepturi repudiandae dolorum earum, aliquam veritatis perferendis nobis.
29 |
30 |
31 |
Aut, eos cum.
32 |
Dolorum, laborum repellat, quis eveniet officia quisquam hic, consequuntur error voluptatem fuga voluptates nulla ullam cupiditate necessitatibus atque obcaecati rerum?
33 |
34 |
35 |
Quas, vitae quam!
36 |
Quas unde veniam eius saepe tempora placeat recusandae dolorum corporis aspernatur quam, impedit aliquam, cumque deleniti assumenda voluptas quae officia?
37 |
38 |
39 | -------------------------------------------------------------------------------- /test/project/src/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Pangolin.js 3 | --- 4 | 5 | Let's have a look at the Fractal design system framework. 6 | 7 | ## Include a component 8 | 9 | And this is a button thingy: 10 | 11 | ```jinja 12 | {% view '@button' %} 13 | ``` 14 | 15 | …which renders to this default variant: 16 | 17 | ```html 18 | {% render '@button' %} 19 | ``` 20 | -------------------------------------------------------------------------------- /test/project/src/main.js: -------------------------------------------------------------------------------- 1 | import './components/button/button.js' 2 | import './components/media/image.js' 3 | -------------------------------------------------------------------------------- /test/project/src/main.scss: -------------------------------------------------------------------------------- 1 | @use "setup/variables.scss"; 2 | @use "setup/fonts.scss"; 3 | @use "setup/scaffolding.scss"; 4 | 5 | @use "components/button/button.scss"; 6 | @use "components/media/image.scss"; 7 | -------------------------------------------------------------------------------- /test/project/src/setup/JetBrainsMono-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolinjs/core/e4d157cc1a476e845c4e87a1f452831e119b0289/test/project/src/setup/JetBrainsMono-Regular.woff -------------------------------------------------------------------------------- /test/project/src/setup/JetBrainsMono-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolinjs/core/e4d157cc1a476e845c4e87a1f452831e119b0289/test/project/src/setup/JetBrainsMono-Regular.woff2 -------------------------------------------------------------------------------- /test/project/src/setup/fonts.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "JetBrains Mono"; 3 | src: 4 | url("JetBrainsMono-Regular.woff2") format("woff2"), 5 | url("JetBrainsMono-Regular.woff") format("woff"); 6 | } 7 | -------------------------------------------------------------------------------- /test/project/src/setup/scaffolding.scss: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: "JetBrains Mono", monospace; 3 | } 4 | -------------------------------------------------------------------------------- /test/project/src/setup/variables.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --darkgreen: hsl(120deg 100% 20%); 3 | --lightgreen: hsl(120deg 73% 75%); 4 | } 5 | -------------------------------------------------------------------------------- /test/unit/lib/copy-dir.spec.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises' 2 | import path from 'path' 3 | import test from 'ava' 4 | 5 | import copyDir from '../../../lib/copy-dir.js' 6 | import getDirname from '../../../lib/get-dirname.js' 7 | 8 | test('copies entire directory', async t => { 9 | const dirname = getDirname(import.meta.url) 10 | const source = path.join(dirname, 'helpers') 11 | const destination = path.join(dirname, '.helpers-temp') 12 | 13 | await fs.mkdir(destination) 14 | await copyDir(source, destination) 15 | const result = await fs.readdir(destination) 16 | t.snapshot(result) 17 | await fs.rm(destination, { recursive: true, force: true }) 18 | }) 19 | -------------------------------------------------------------------------------- /test/unit/lib/format-ip.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import formatIP from '../../../lib/format-ip.js' 4 | 5 | test('formats localhost IPv4', t => { 6 | const result = formatIP('0.0.0.0') 7 | t.is(result, 'localhost') 8 | }) 9 | 10 | test('formats localhost IPv6', t => { 11 | const result = formatIP('::') 12 | t.is(result, 'localhost') 13 | }) 14 | 15 | test('returns non-localhost IP', t => { 16 | const result = formatIP('10.0.0.0') 17 | t.is(result, '10.0.0.0') 18 | }) 19 | -------------------------------------------------------------------------------- /test/unit/lib/generate-asset-filename.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import generateAssetFilename from '../../../lib/generate-asset-filename.js' 4 | 5 | test('generates options', t => { 6 | const result = generateAssetFilename({ type: 'img' }) 7 | t.snapshot(result) 8 | }) 9 | 10 | test('generates options with hash', t => { 11 | const result = generateAssetFilename({ type: 'img', hash: true }) 12 | t.snapshot(result) 13 | }) 14 | -------------------------------------------------------------------------------- /test/unit/lib/generate-output-filename.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import generateOutputFilename from '../../../lib/generate-output-filename.js' 4 | 5 | test('generates output filename', t => { 6 | const result = generateOutputFilename({ type: 'js' }) 7 | t.is(result, 'js/[name].js') 8 | }) 9 | 10 | test('generates output filename with hash', t => { 11 | const result = generateOutputFilename({ type: 'js', hash: 'all' }) 12 | t.is(result, 'js/[contenthash:8].js') 13 | }) 14 | -------------------------------------------------------------------------------- /test/unit/lib/get-asset-files.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import getAssetFiles from '../../../lib/get-asset-files.js' 4 | 5 | test('gets asset files', t => { 6 | const result = getAssetFiles({ 7 | files: ['hello.js', 'world.css', 'universe.svg'] 8 | }) 9 | 10 | t.snapshot(result) 11 | }) 12 | -------------------------------------------------------------------------------- /test/unit/lib/get-config.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import getConfig from '../../../lib/get-config.js' 4 | import getDirname from '../../../lib/get-dirname.js' 5 | 6 | test.serial('gets default config', async t => { 7 | const result = await getConfig({}) 8 | t.snapshot(result) 9 | }) 10 | 11 | test.serial('gets user config', async t => { 12 | const dirname = getDirname(import.meta.url) 13 | const result = await getConfig({ context: dirname + '/helpers' }) 14 | t.snapshot(result) 15 | }) 16 | -------------------------------------------------------------------------------- /test/unit/lib/get-dirname.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import getDirname from '../../../lib/get-dirname.js' 4 | 5 | test('gets current file’s dirname', t => { 6 | const result = getDirname(import.meta.url) 7 | t.true(result.endsWith('/test/unit/lib')) 8 | }) 9 | -------------------------------------------------------------------------------- /test/unit/lib/get-host-ips.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import getHostIPs from '../../../lib/get-host-ips.js' 4 | 5 | test('gets host IPs', t => { 6 | const result = getHostIPs() 7 | 8 | for (const ip of result) { 9 | t.true(typeof ip === 'string') 10 | } 11 | }) 12 | -------------------------------------------------------------------------------- /test/unit/lib/get-path.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import getPaths from '../../../lib/get-paths.js' 4 | 5 | test('gets paths', t => { 6 | const result = getPaths({ context: 'test' }) 7 | t.snapshot(result) 8 | }) 9 | -------------------------------------------------------------------------------- /test/unit/lib/get-pkg.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import getDirname from '../../../lib/get-dirname.js' 4 | import getPkg from '../../../lib/get-pkg.js' 5 | 6 | test('gets package.json', async t => { 7 | const dirname = getDirname(import.meta.url) 8 | const result = await getPkg({ context: dirname + '/helpers' }) 9 | t.snapshot(result) 10 | }) 11 | -------------------------------------------------------------------------------- /test/unit/lib/get-port.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import getPort from '../../../lib/get-port.js' 4 | 5 | test('gets unused port', async t => { 6 | const result = await getPort(1337) 7 | t.is(result, 1337) 8 | }) 9 | -------------------------------------------------------------------------------- /test/unit/lib/helpers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test", 3 | "version": "1.0.0", 4 | "author": { 5 | "name": "Test Author" 6 | }, 7 | "type": "module" 8 | } 9 | -------------------------------------------------------------------------------- /test/unit/lib/helpers/pangolin.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | ui: { 3 | color: 'teal' 4 | }, 5 | 6 | hashFiles: 'all' 7 | } 8 | -------------------------------------------------------------------------------- /test/unit/lib/is-data-type.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import isDataType from '../../../lib/is-data-type.js' 4 | 5 | test('detects objects', t => { 6 | const result = isDataType({}, Object) 7 | t.true(result) 8 | }) 9 | 10 | test('detects arrays', t => { 11 | const result = isDataType([], Array) 12 | t.true(result) 13 | }) 14 | 15 | test('detects dates', t => { 16 | const result = isDataType(new Date(), Date) 17 | t.true(result) 18 | }) 19 | 20 | test('detects functions', t => { 21 | const result = isDataType(() => {}, Function) 22 | t.true(result) 23 | }) 24 | 25 | test('detects strings', t => { 26 | const result = isDataType('', String) 27 | t.true(result) 28 | }) 29 | 30 | test('detects numbers', t => { 31 | const result = isDataType(0, Number) 32 | t.true(result) 33 | }) 34 | 35 | test('detects booleans', t => { 36 | const result = isDataType(true, Boolean) 37 | t.true(result) 38 | }) 39 | 40 | test('detects symbols', t => { 41 | const result = isDataType(Symbol(''), Symbol) 42 | t.true(result) 43 | }) 44 | -------------------------------------------------------------------------------- /test/unit/lib/merge-objects.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import mergeObjects from '../../../lib/merge-objects.js' 4 | 5 | test('shallow merges', t => { 6 | const target = { 7 | hello: 'world' 8 | } 9 | 10 | const source = { 11 | hello: 'galaxy', 12 | foo: 'bar' 13 | } 14 | 15 | const result = mergeObjects(target, source) 16 | t.snapshot(result) 17 | }) 18 | 19 | test('deep merges', t => { 20 | const target = { 21 | species: { 22 | me: 'Human', 23 | you: 'Wookie' 24 | } 25 | } 26 | 27 | const source = { 28 | species: { 29 | you: 'Togruta' 30 | } 31 | } 32 | 33 | const result = mergeObjects(target, source) 34 | t.snapshot(result) 35 | }) 36 | -------------------------------------------------------------------------------- /test/unit/lib/pangolin-head-extension.spec.js: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | 3 | import PangolinHeadExtension from '../../../lib/pangolin-head-extension.js' 4 | 5 | test.serial('creates markup in dev mode', t => { 6 | const nodeEnvCopy = process.env.NODE_ENV 7 | process.env.NODE_ENV = 'development' 8 | 9 | const publicPath = '/hello/world/' 10 | const assets = { js: [], css: [] } 11 | 12 | const result = new PangolinHeadExtension({ publicPath, assets }) 13 | t.snapshot(result.run().val) 14 | 15 | process.env.NODE_ENV = nodeEnvCopy 16 | }) 17 | 18 | test.serial('creates markup in prod mode', t => { 19 | const publicPath = '/hello/world/' 20 | const assets = { 21 | js: ['vendor.js', 'app.js'], 22 | css: ['vendor.css', 'app.css'] 23 | } 24 | 25 | const result = new PangolinHeadExtension({ publicPath, assets }) 26 | 27 | t.deepEqual(result.tags, ['pangolin_head']) 28 | t.snapshot(result.run().val) 29 | }) 30 | -------------------------------------------------------------------------------- /test/unit/lib/snapshots/copy-dir.spec.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/unit/lib/copy-dir.spec.js` 2 | 3 | The actual snapshot is saved in `copy-dir.spec.js.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## copies entire directory 8 | 9 | > Snapshot 1 10 | 11 | [ 12 | 'package.json', 13 | 'pangolin.config.js', 14 | ] 15 | -------------------------------------------------------------------------------- /test/unit/lib/snapshots/copy-dir.spec.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolinjs/core/e4d157cc1a476e845c4e87a1f452831e119b0289/test/unit/lib/snapshots/copy-dir.spec.js.snap -------------------------------------------------------------------------------- /test/unit/lib/snapshots/generate-asset-filename.spec.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/unit/lib/generate-asset-filename.spec.js` 2 | 3 | The actual snapshot is saved in `generate-asset-filename.spec.js.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## generates options 8 | 9 | > Snapshot 1 10 | 11 | 'img/[name][ext]' 12 | 13 | ## generates options with hash 14 | 15 | > Snapshot 1 16 | 17 | 'img/[contenthash:8][ext]' 18 | -------------------------------------------------------------------------------- /test/unit/lib/snapshots/generate-asset-filename.spec.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolinjs/core/e4d157cc1a476e845c4e87a1f452831e119b0289/test/unit/lib/snapshots/generate-asset-filename.spec.js.snap -------------------------------------------------------------------------------- /test/unit/lib/snapshots/get-asset-files.spec.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/unit/lib/get-asset-files.spec.js` 2 | 3 | The actual snapshot is saved in `get-asset-files.spec.js.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## gets asset files 8 | 9 | > Snapshot 1 10 | 11 | { 12 | css: [ 13 | 'world.css', 14 | ], 15 | js: [ 16 | 'hello.js', 17 | ], 18 | } 19 | -------------------------------------------------------------------------------- /test/unit/lib/snapshots/get-asset-files.spec.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolinjs/core/e4d157cc1a476e845c4e87a1f452831e119b0289/test/unit/lib/snapshots/get-asset-files.spec.js.snap -------------------------------------------------------------------------------- /test/unit/lib/snapshots/get-config.spec.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/unit/lib/get-config.spec.js` 2 | 3 | The actual snapshot is saved in `get-config.spec.js.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## gets default config 8 | 9 | > Snapshot 1 10 | 11 | { 12 | hashFiles: 'imported', 13 | project: { 14 | author: null, 15 | base: '/assets/', 16 | name: 'Pangolin.js', 17 | version: null, 18 | }, 19 | ui: { 20 | color: { 21 | accent: '#ff721f', 22 | complement: '#000', 23 | links: '#7f390f', 24 | }, 25 | favicon: '/favicon.ico', 26 | format: 'json', 27 | information: [ 28 | { 29 | label: 'Version', 30 | value: null, 31 | }, 32 | { 33 | format: Function format {}, 34 | label: 'Built on', 35 | type: 'time', 36 | value: Date 2000-01-01 00:00:00 UTC {}, 37 | }, 38 | ], 39 | labels: { 40 | panels: { 41 | html: 'Render', 42 | view: 'Source', 43 | }, 44 | }, 45 | lang: 'en', 46 | }, 47 | } 48 | 49 | ## gets user config 50 | 51 | > Snapshot 1 52 | 53 | { 54 | hashFiles: 'all', 55 | project: { 56 | author: 'Test Author', 57 | base: '/assets/', 58 | name: 'test', 59 | version: '1.0.0', 60 | }, 61 | ui: { 62 | color: 'teal', 63 | favicon: '/favicon.ico', 64 | format: 'json', 65 | information: [ 66 | { 67 | label: 'Version', 68 | value: '1.0.0', 69 | }, 70 | { 71 | format: Function format {}, 72 | label: 'Built on', 73 | type: 'time', 74 | value: Date 2000-01-01 00:00:00 UTC {}, 75 | }, 76 | ], 77 | labels: { 78 | panels: { 79 | html: 'Render', 80 | view: 'Source', 81 | }, 82 | }, 83 | lang: 'en', 84 | }, 85 | } 86 | -------------------------------------------------------------------------------- /test/unit/lib/snapshots/get-config.spec.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolinjs/core/e4d157cc1a476e845c4e87a1f452831e119b0289/test/unit/lib/snapshots/get-config.spec.js.snap -------------------------------------------------------------------------------- /test/unit/lib/snapshots/get-path.spec.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/unit/lib/get-path.spec.js` 2 | 3 | The actual snapshot is saved in `get-path.spec.js.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## gets paths 8 | 9 | > Snapshot 1 10 | 11 | { 12 | inputComponents: 'test/src/components', 13 | inputDocs: 'test/src/docs', 14 | inputPublic: 'test/public', 15 | outputAssets: 'test/public/assets', 16 | outputBuild: 'test/dist', 17 | outputStatic: 'test/static', 18 | } 19 | -------------------------------------------------------------------------------- /test/unit/lib/snapshots/get-path.spec.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolinjs/core/e4d157cc1a476e845c4e87a1f452831e119b0289/test/unit/lib/snapshots/get-path.spec.js.snap -------------------------------------------------------------------------------- /test/unit/lib/snapshots/get-pkg.spec.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/unit/lib/get-pkg.spec.js` 2 | 3 | The actual snapshot is saved in `get-pkg.spec.js.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## gets package.json 8 | 9 | > Snapshot 1 10 | 11 | { 12 | author: { 13 | name: 'Test Author', 14 | }, 15 | name: 'test', 16 | type: 'module', 17 | version: '1.0.0', 18 | } 19 | -------------------------------------------------------------------------------- /test/unit/lib/snapshots/get-pkg.spec.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolinjs/core/e4d157cc1a476e845c4e87a1f452831e119b0289/test/unit/lib/snapshots/get-pkg.spec.js.snap -------------------------------------------------------------------------------- /test/unit/lib/snapshots/merge-objects.spec.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/unit/lib/merge-objects.spec.js` 2 | 3 | The actual snapshot is saved in `merge-objects.spec.js.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## shallow merges 8 | 9 | > Snapshot 1 10 | 11 | { 12 | foo: 'bar', 13 | hello: 'galaxy', 14 | } 15 | 16 | ## deep merges 17 | 18 | > Snapshot 1 19 | 20 | { 21 | species: { 22 | me: 'Human', 23 | you: 'Togruta', 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /test/unit/lib/snapshots/merge-objects.spec.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolinjs/core/e4d157cc1a476e845c4e87a1f452831e119b0289/test/unit/lib/snapshots/merge-objects.spec.js.snap -------------------------------------------------------------------------------- /test/unit/lib/snapshots/pangolin-head-extension.spec.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `test/unit/lib/pangolin-head-extension.spec.js` 2 | 3 | The actual snapshot is saved in `pangolin-head-extension.spec.js.snap`. 4 | 5 | Generated by [AVA](https://avajs.dev). 6 | 7 | ## creates markup in dev mode 8 | 9 | > Snapshot 1 10 | 11 | '' 12 | 13 | ## creates markup in prod mode 14 | 15 | > Snapshot 1 16 | 17 | `␊ 18 | ␊ 19 | ␊ 20 | ` 21 | -------------------------------------------------------------------------------- /test/unit/lib/snapshots/pangolin-head-extension.spec.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pangolinjs/core/e4d157cc1a476e845c4e87a1f452831e119b0289/test/unit/lib/snapshots/pangolin-head-extension.spec.js.snap -------------------------------------------------------------------------------- /test/unit/setup/date.js: -------------------------------------------------------------------------------- 1 | const RealDate = Date 2 | 3 | /** 4 | * Return static date for `new Date()` 5 | */ 6 | global.Date = class Date { 7 | constructor (date) { 8 | if (date) { 9 | return new RealDate(date) 10 | } 11 | 12 | return new RealDate('2000-01-01T00:00:00.000Z') 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /webpack/base.js: -------------------------------------------------------------------------------- 1 | import Config from 'webpack-chain' 2 | import sass from 'sass' 3 | import webpack from 'webpack' 4 | import WebpackBar from 'webpackbar' 5 | 6 | import getConfig from '../lib/get-config.js' 7 | import generateAssetFilename from '../lib/generate-asset-filename.js' 8 | 9 | /** 10 | * Base webpack configuration 11 | * @param {Object} options Options 12 | * @param {string} options.context Working directory 13 | */ 14 | export default async function ({ context }) { 15 | const projectConfig = await getConfig({ context }) 16 | const webpackConfig = new Config() 17 | 18 | /* eslint-disable indent */ 19 | 20 | webpackConfig.context(context) 21 | 22 | webpackConfig.entry('main') 23 | .add('./src/main.js') 24 | .add('./src/main.scss') 25 | 26 | webpackConfig.output 27 | .publicPath(projectConfig.project.base) 28 | 29 | webpackConfig.resolve 30 | .extensions 31 | .add('.js') 32 | .add('.mjs') 33 | .add('.json') 34 | .end() 35 | 36 | // JS 37 | 38 | webpackConfig.module.rule('js') 39 | .test(/\.m?js$/) 40 | .exclude 41 | .add(/node_modules/) 42 | .end() 43 | .use('babel-loader') 44 | .loader('babel-loader') 45 | .end() 46 | 47 | // CSS 48 | 49 | webpackConfig.module.rule('css') 50 | .test(/\.s?css$/) 51 | .use('css-loader') 52 | .loader('css-loader') 53 | .options({ 54 | importLoaders: 3 55 | }) 56 | .end() 57 | .use('postcss-loader') 58 | .loader('postcss-loader') 59 | .end() 60 | .use('resolve-url-loader') 61 | .loader('resolve-url-loader') 62 | .end() 63 | .use('sass-loader') 64 | .loader('sass-loader') 65 | .options({ 66 | implementation: sass 67 | }) 68 | .end() 69 | 70 | // Static assets 71 | 72 | webpackConfig.module 73 | .rule('img') 74 | .test(/\.(png|jpe?g|webp|avif|svg)(\?.*)?$/) 75 | .type('asset/resource') 76 | .set('generator', { 77 | filename: generateAssetFilename({ type: 'img', hash: projectConfig.hashFiles }) 78 | }) 79 | 80 | webpackConfig.module 81 | .rule('video') 82 | .test(/\.(mp4|webm)(\?.*)?$/) 83 | .type('asset/resource') 84 | .set('generator', { 85 | filename: generateAssetFilename({ type: 'video', hash: projectConfig.hashFiles }) 86 | }) 87 | 88 | webpackConfig.module 89 | .rule('audio') 90 | .test(/\.(ogg|mp3|wav|flac|aac)(\?.*)?$/) 91 | .type('asset/resource') 92 | .set('generator', { 93 | filename: generateAssetFilename({ type: 'audio', hash: projectConfig.hashFiles }) 94 | }) 95 | 96 | webpackConfig.module 97 | .rule('font') 98 | .test(/\.(woff2?)(\?.*)?$/) 99 | .type('asset/resource') 100 | .set('generator', { 101 | filename: generateAssetFilename({ type: 'font', hash: projectConfig.hashFiles }) 102 | }) 103 | 104 | // Plugins 105 | 106 | webpackConfig.plugin('env') 107 | .use(webpack.EnvironmentPlugin, [ 108 | 'NODE_ENV' 109 | ]) 110 | 111 | webpackConfig.plugin('progress') 112 | .use(WebpackBar, [{ 113 | name: 'Pangolin.js', 114 | color: '#ff721f', 115 | profile: true 116 | }]) 117 | 118 | /* eslint-enable indent */ 119 | 120 | return webpackConfig 121 | } 122 | -------------------------------------------------------------------------------- /webpack/build.js: -------------------------------------------------------------------------------- 1 | import { WebpackManifestPlugin } from 'webpack-manifest-plugin' 2 | import BundleAnalyzer from 'webpack-bundle-analyzer' 3 | import CSSExtractPlugin from 'mini-css-extract-plugin' 4 | import cssnano from 'cssnano' 5 | 6 | import generateOutputFilename from '../lib/generate-output-filename.js' 7 | import getConfig from '../lib/get-config.js' 8 | import getPaths from '../lib/get-paths.js' 9 | import webpackBaseConfig from './base.js' 10 | 11 | /** 12 | * Production webpack configuration 13 | * @param {Object} options Options 14 | * @param {string} options.context Working directory 15 | */ 16 | export default async function ({ context }) { 17 | const projectConfig = await getConfig({ context }) 18 | const webpackConfig = await webpackBaseConfig({ context }) 19 | const paths = getPaths({ context }) 20 | 21 | /* eslint-disable indent */ 22 | 23 | webpackConfig 24 | .mode('production') 25 | .devtool('source-map') 26 | 27 | webpackConfig.output 28 | .path(paths.outputAssets) 29 | .filename(generateOutputFilename({ 30 | type: 'js', 31 | hash: projectConfig.hashFiles 32 | })) 33 | 34 | // CSS 35 | 36 | webpackConfig.module.rule('css') 37 | .use('css-extract-loader') 38 | .before('css-loader') 39 | .loader(CSSExtractPlugin.loader) 40 | .end() 41 | .use('cssnano-loader') 42 | .after('css-loader') 43 | .loader('postcss-loader') 44 | .options({ 45 | postcssOptions: { 46 | plugins: [cssnano({ 47 | preset: ['default', { 48 | mergeLonghand: false, 49 | mergeRules: false 50 | }] 51 | })] 52 | } 53 | }) 54 | .end() 55 | 56 | // Plugins 57 | 58 | webpackConfig.plugin('css-extract') 59 | .use(CSSExtractPlugin, [{ 60 | filename: generateOutputFilename({ 61 | type: 'css', 62 | hash: projectConfig.hashFiles 63 | }) 64 | }]) 65 | 66 | webpackConfig.plugin('manifest') 67 | .use(WebpackManifestPlugin) 68 | 69 | webpackConfig.plugin('bundle-analyzer') 70 | .use(BundleAnalyzer.BundleAnalyzerPlugin, [{ 71 | analyzerMode: 'static', 72 | openAnalyzer: false 73 | }]) 74 | 75 | /* eslint-enable indent */ 76 | 77 | return webpackConfig 78 | } 79 | -------------------------------------------------------------------------------- /webpack/dev.js: -------------------------------------------------------------------------------- 1 | import { blue, green } from 'kleur/colors' 2 | import FriendlyErrorsPlugin from '@nuxt/friendly-errors-webpack-plugin' 3 | 4 | import formatIP from '../lib/format-ip.js' 5 | import getHostIPs from '../lib/get-host-ips.js' 6 | import webpackBaseConfig from './base.js' 7 | 8 | /** 9 | * Development webpack configuration 10 | * @param {Object} options Options 11 | * @param {string} options.context Working directory 12 | * @param {string} options.host Here it runs 13 | * @param {number} options.port Where webpack serves files 14 | */ 15 | export default async function ({ context, host, port }) { 16 | const webpackConfig = await webpackBaseConfig({ context }) 17 | const networkIPs = getHostIPs() 18 | 19 | /* eslint-disable indent */ 20 | 21 | webpackConfig 22 | .mode('development') 23 | .devtool('eval-cheap-module-source-map') 24 | .stats('none') 25 | .set('infrastructureLogging', { level: 'warn' }) 26 | 27 | webpackConfig.output 28 | .filename('main.js') 29 | 30 | webpackConfig.module.rule('css') 31 | .use('style-loader') 32 | .before('css-loader') 33 | .loader('style-loader') 34 | .end() 35 | 36 | webpackConfig.plugin('friendly-errors') 37 | .use(FriendlyErrorsPlugin, [{ 38 | compilationSuccessInfo: { 39 | messages: [ 40 | 'Pangolin.js dev server running at:', 41 | ' - Local: ' + blue(`http://${formatIP(host)}:${port}`), 42 | ...networkIPs.map(ip => ' - Network: ' + blue(`http://${ip}:${port}`)) 43 | ], 44 | notes: [ 45 | 'Note that the development build is not optimized.', 46 | `To create a production build, run ${green('npm run build')}.` 47 | ] 48 | } 49 | }]) 50 | 51 | /* eslint-enable indent */ 52 | 53 | return webpackConfig 54 | } 55 | --------------------------------------------------------------------------------