├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ └── translator.yaml ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .npmrc ├── .prettierrc.json ├── LICENSE ├── README.md ├── commitlint.config.js ├── example ├── error │ ├── package.json │ ├── plugins │ │ └── test │ │ │ ├── package.json │ │ │ ├── src │ │ │ └── index.ts │ │ │ └── tsconfig.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── fallback │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── full │ ├── package.json │ ├── plugins │ │ ├── alert │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ └── index.ts │ │ │ ├── tsconfig.json │ │ │ ├── typings │ │ │ │ └── index.d.ts │ │ │ └── yarn.lock │ │ ├── core │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ ├── async.ts │ │ │ │ ├── index.ts │ │ │ │ └── sync.ts │ │ │ ├── tsconfig.json │ │ │ └── typings │ │ │ │ └── index.d.ts │ │ └── test │ │ │ ├── package.json │ │ │ ├── src │ │ │ └── index.ts │ │ │ ├── tsconfig.json │ │ │ └── typings │ │ │ └── index.d.ts │ ├── src │ │ ├── console-ui.ts │ │ ├── global.d.ts │ │ ├── index.css │ │ ├── index.ts │ │ ├── shared.ts │ │ └── toolbar.ts │ └── tsconfig.json ├── simple │ ├── package.json │ ├── plugins │ │ └── test │ │ │ ├── package.json │ │ │ ├── src │ │ │ └── index.ts │ │ │ └── tsconfig.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── vue3 │ ├── .gitignore │ ├── .ministarrc.js │ ├── .npmrc │ ├── index.html │ ├── package.json │ ├── plugins │ │ └── test │ │ │ ├── package.json │ │ │ └── src │ │ │ ├── Component.vue │ │ │ └── index.js │ ├── public │ │ └── favicon.ico │ ├── src │ │ ├── App.vue │ │ ├── assets │ │ │ └── logo.png │ │ ├── components │ │ │ └── HelloWorld.vue │ │ ├── main.js │ │ └── reg.js │ └── vite.config.js └── webpack.config.js ├── jest.config.js ├── mini-star ├── .gitignore ├── .npmrc ├── README.md ├── build │ └── replace-browser-modules.ts ├── bundler │ ├── browser │ │ ├── README.md │ │ └── entry.ts │ ├── bundle.ts │ ├── config.ts │ ├── rollup.config.ts │ ├── types.ts │ └── utils.ts ├── cli │ ├── create-plugin.ts │ ├── index.ts │ └── utils.ts ├── jest.config.js ├── package.json ├── rollup.browser.ts ├── runtime │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── utils.spec.ts.snap │ │ └── utils.spec.ts │ ├── config.ts │ ├── helper.ts │ ├── index.ts │ ├── init.ts │ ├── loader.ts │ ├── types.ts │ └── utils.ts ├── tsconfig.build.json ├── tsconfig.json └── types │ └── rollup__plugin-url │ └── index.d.ts ├── package.json ├── playground ├── .gitignore ├── index.html ├── package.json ├── postcss.config.cjs ├── public │ ├── favicon.ico │ └── logo.svg ├── src │ ├── App.tsx │ ├── components │ │ ├── Editor.tsx │ │ └── Previewer.tsx │ ├── defaultFiles │ │ ├── defaultCapitalApp.tsx │ │ └── defaultMiniStarRC.json │ ├── index.css │ ├── main.tsx │ ├── tailwind.css │ ├── utils │ │ ├── path.ts │ │ └── version.ts │ └── vite-env.d.ts ├── tailwind.config.cjs ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── test ├── plugins │ ├── async │ │ ├── __snapshots__ │ │ │ └── async.spec.ts.snap │ │ ├── async.spec.ts │ │ ├── package.json │ │ └── plugins │ │ │ └── demo │ │ │ ├── package.json │ │ │ ├── src │ │ │ ├── async.ts │ │ │ └── index.ts │ │ │ └── tsconfig.json │ ├── auto-load │ │ ├── __snapshots__ │ │ │ └── auto-load.spec.ts.snap │ │ ├── auto-load.spec.ts │ │ ├── capital │ │ │ └── src │ │ │ │ └── index.ts │ │ ├── package.json │ │ └── plugins │ │ │ ├── first │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ │ ├── second │ │ │ ├── package.json │ │ │ ├── src │ │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ │ └── third │ │ │ ├── package.json │ │ │ ├── src │ │ │ └── index.ts │ │ │ └── tsconfig.json │ └── simple │ │ ├── __snapshots__ │ │ └── simple.spec.ts.snap │ │ ├── capital │ │ └── src │ │ │ └── index.ts │ │ ├── package.json │ │ ├── plugins │ │ └── demo │ │ │ ├── package.json │ │ │ ├── src │ │ │ └── index.ts │ │ │ └── tsconfig.json │ │ └── simple.spec.ts ├── utils.ts └── webpack.test.config.js ├── tsconfig.test.json └── website ├── .gitignore ├── .npmrc ├── .yarnrc ├── README.md ├── babel.config.js ├── blog ├── 2019-05-28-hola.md ├── 2019-05-29-hello-world.md └── 2019-05-30-welcome.md ├── docs └── tutorial │ ├── example.md │ ├── guide │ ├── _category_.json │ ├── buildFile.md │ ├── loadLater.md │ ├── ministarrc.md │ ├── pluginDependencies.md │ ├── reduceEntry.md │ ├── shareDependencies.md │ └── webpack.md │ ├── intro.md │ ├── quickstart │ ├── _category_.json │ ├── createPlugin.md │ ├── install.md │ └── usePlugin.md │ ├── speech │ ├── _category_.json │ └── index.md │ └── why.md ├── docusaurus.config.js ├── i18n └── zh-Hans │ ├── code.json │ ├── docusaurus-plugin-content-docs │ ├── current.json │ └── current │ │ └── tutorial │ │ ├── example.md │ │ ├── guide │ │ ├── buildFile.md │ │ ├── loadLater.md │ │ ├── ministarrc.md │ │ ├── pluginDependencies.md │ │ ├── reduceEntry.md │ │ ├── shareDependencies.md │ │ └── webpack.md │ │ ├── intro.md │ │ ├── quickstart │ │ ├── createPlugin.md │ │ ├── install.md │ │ └── usePlugin.md │ │ └── why.md │ └── docusaurus-theme-classic │ ├── footer.json │ └── navbar.json ├── package.json ├── scripts └── moveSpeech.js ├── sidebars.js ├── speech └── intro │ ├── .gitignore │ ├── .npmrc │ ├── README.md │ ├── netlify.toml │ ├── package.json │ ├── public │ ├── chrome.jpeg │ └── vscode.png │ ├── slides.md │ ├── vercel.json │ └── yarn.lock ├── src ├── components │ ├── HomepageFeatures.module.css │ └── HomepageFeatures.tsx ├── css │ └── custom.css └── pages │ ├── index.module.css │ ├── index.tsx │ └── markdown-page.md ├── static ├── .nojekyll └── img │ ├── docs │ ├── createPlugin.jpg │ ├── intro.png │ └── usePlugin.jpg │ ├── docusaurus.png │ ├── favicon.ico │ ├── logo.png │ ├── logo.svg │ ├── tutorial │ ├── docsVersionDropdown.png │ └── localeDropdown.png │ ├── undraw_Dev_focus_re_6iwt.svg │ ├── undraw_JavaScript_frameworks_8qpc.svg │ ├── undraw_Mobile_marketing_re_p77p.svg │ ├── undraw_docusaurus_mountain.svg │ ├── undraw_docusaurus_react.svg │ ├── undraw_docusaurus_tree.svg │ └── undraw_product_teardown_elol.svg ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [.gitconfig] 12 | indent_style = tab 13 | 14 | [Makefile] 15 | indent_style = tab 16 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.js 2 | 3 | # playground 先不走eslint 4 | playground/ 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * https://robertcooper.me/post/using-eslint-and-prettier-in-a-typescript-project 3 | */ 4 | 5 | module.exports = { 6 | parser: '@typescript-eslint/parser', // Specifies the ESLint parser 7 | parserOptions: { 8 | ecmaVersion: 2020, // Allows for the parsing of modern ECMAScript features 9 | sourceType: 'module', // Allows for the use of imports 10 | ecmaFeatures: { 11 | jsx: true, // Allows for the parsing of JSX 12 | }, 13 | }, 14 | settings: { 15 | react: { 16 | version: 'detect', // Tells eslint-plugin-react to automatically detect the version of React to use 17 | }, 18 | }, 19 | extends: [ 20 | 'plugin:react/recommended', // Uses the recommended rules from @eslint-plugin-react 21 | 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from the @typescript-eslint/eslint-plugin 22 | 'prettier', // Uses eslint-config-prettier to disable ESLint rules from @typescript-eslint/eslint-plugin that would conflict with prettier 23 | 'plugin:prettier/recommended', // Enables eslint-plugin-prettier and eslint-config-prettier. This will display prettier errors as ESLint errors. Make sure this is always the last configuration in the extends array. 24 | ], 25 | rules: { 26 | // Place to specify ESLint rules. Can be used to overwrite rules specified from the extended configs 27 | // e.g. "@typescript-eslint/explicit-function-return-type": "off", 28 | '@typescript-eslint/explicit-module-boundary-types': 'off', 29 | '@typescript-eslint/no-empty-function': 'off', 30 | '@typescript-eslint/no-var-requires': 'off', 31 | 'react/prop-types': 'off', 32 | '@typescript-eslint/ban-ts-comment': 'off', 33 | '@typescript-eslint/ban-types': 'off', 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /.github/workflows/translator.yaml: -------------------------------------------------------------------------------- 1 | name: 'translator' 2 | 3 | on: 4 | issues: 5 | types: [opened, edited] 6 | issue_comment: 7 | types: [created, edited] 8 | discussion: 9 | types: [created, edited] 10 | discussion_comment: 11 | types: [created, edited] 12 | pull_request_target: 13 | types: [opened, edited] 14 | pull_request_review_comment: 15 | types: [created, edited] 16 | 17 | jobs: 18 | translate: 19 | permissions: 20 | issues: write 21 | discussions: write 22 | pull-requests: write 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: lizheming/github-translate-action@1.1.2 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | with: 29 | IS_MODIFY_TITLE: true 30 | APPEND_TRANSLATION: true 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | lerna-debug.log 4 | package-lock.json 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit "$1" 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | pnpm lint-staged 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry = https://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true, 9 | "arrowParens": "always", 10 | "parser": "babel", 11 | "jsxBracketSameLine": false, 12 | "overrides": [ 13 | { 14 | "files": ["*.json", ".babelrc", ".prettierrc.json"], 15 | "options": { 16 | "parser": "json" 17 | } 18 | }, 19 | { 20 | "files": "*.{tsx,ts}", 21 | "options": { 22 | "parser": "typescript" 23 | } 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 moonrailgun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MiniStar 2 | 3 | A framework which can help your project pluginize. 4 | 5 | Special Mark: 6 | - `@capital/*` Base project exported dependencies 7 | - `@plugins/*` Plugin project exported dependencies 8 | 9 | For more detail, check document: [https://ministar.moonrailgun.com/](https://ministar.moonrailgun.com/) 10 | 11 | ## Intro 12 | 13 | `mini-star` is a front-end library for the realization of the project's micro-kernel(pluginize), which aims to help you build(or migrate to) a production-usable micro-kernel (plug-in) architecture system more simply and painlessly. 14 | 15 | ## What is Microkernel Architecture 16 | 17 | It is composed of a group of software programs that minimize the number as much as possible, and they are responsible for providing and implementing various mechanisms and functions required by an operating system. These most basic mechanisms include low-level address space management, thread management, and inter-process communication. 18 | 19 | ![Microkernel Architecture](./website/static/img/docs/intro.png) 20 | 21 | ## Design concept 22 | 23 | The realization of the system is distinguished from the basic operating rules of the system. The way it is implemented is to modularize the core functions, divide them into several independent processes, and run them separately. These processes are called services. All service processes are running in different address spaces. 24 | 25 | Making services independent of each other can reduce the degree of coupling between systems, facilitate implementation and debugging, and improve portability. It can prevent a single component from failing and causing the entire system to crash. The kernel only needs to restart this component, which will not affect the functions of other servers and increase the stability of the system. At the same time, business functions can be replaced or added to certain service processes as needed to make the functions more flexible. 26 | 27 | In terms of the amount of code, generally speaking, because of simplified functions, the core system uses less code than the integrated system. Less code means fewer hidden bugs. 28 | 29 | ## ministar's design concept 30 | 31 | - Simple 32 | 33 | Since both the base project and the plug-in project can achieve the technology stack irrelevant, `ministar` is just a library similar to the jQuery plug-in system for users. You need to load the plug-in and shared dependent components through `ministar/runtime`, and then use `ministar/ Bundler` can build the plug-in project to realize the plug-in transformation of the original system. 34 | 35 | - Decoupling/Technology Stack Independent 36 | 37 | The core goal of the microkernel is the same as that of the micro front end. It is to disassemble the boulder application into a number of loosely coupled micro applications that can be autonomous. Many designs of ministar adhere to this principle, except for the shared public dependencies and the base project. Ability, the plug-in project has its own context, dependency management, and mutual communication mechanism, so as to ensure that the plug-in has the ability to develop independently. And to ensure the ability to share types with other dependencies. 38 | 39 | ## Feature 40 | 41 | - Out of the box, it can also be customized. 42 | - The technology stack has nothing to do with the application of any technology stack can be `used/accessed`, whether it is React/Vue/Angular/Svelte/JQuery or other frameworks. 43 | - Shared dependencies, the same dependencies only need to be loaded once, reducing unnecessary volume and packaging time 44 | - Make dependency calls between plugins like calling native components 45 | - Packing based on `Rollup`, fast! 46 | - Born for the modern front end. In the past, we exposed methods through windows, now all our code needs to be compiled into modules, and exposure is also through modules 47 | - Topology relies on sorting to prevent timing problems 48 | 49 | ## Who is using MiniStar 50 | 51 | - [tailchat](https://github.com/msgbyte/tailchat) 52 | - [TRPGEngine](https://github.com/TRPGEngine/Client) 53 | - [codeck](https://github.com/moonrailgun/codeck) 54 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * document: https://commitlint.js.org/#/reference-configuration 3 | * 4 | * https://github.com/angular/angular/blob/master/CONTRIBUTING.md#type 5 | * feat:新功能(feature) 6 | * fix:修补bug 7 | * docs:文档(documentation) 8 | * style: 格式(不影响代码运行的变动) 9 | * refactor:重构(即不是新增功能,也不是修改bug的代码变动) 10 | * perf:性能优化 11 | * test:增加测试 12 | * chore:构建过程或辅助工具的变动 13 | */ 14 | 15 | module.exports = { 16 | extends: ['@commitlint/config-conventional'], 17 | rules: { 18 | 'subject-case': [0], 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /example/error/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-error", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "", 6 | "main": "index.js", 7 | "scripts": { 8 | "build": "webpack --config ../webpack.config.js --entry ./src/index.ts --output-path ./dist", 9 | "build:plugins": "ministar buildPlugin all", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "author": "moonrailgun", 13 | "license": "MIT", 14 | "dependencies": { 15 | "mini-star": "*" 16 | }, 17 | "devDependencies": { 18 | "webpack": "^5.38.1", 19 | "webpack-cli": "^4.7.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /example/error/plugins/test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plugins/test", 3 | "main": "src/index.ts", 4 | "version": "0.0.0", 5 | "private": true, 6 | "dependencies": {} 7 | } -------------------------------------------------------------------------------- /example/error/plugins/test/src/index.ts: -------------------------------------------------------------------------------- 1 | throw new Error('Foo'); 2 | 3 | console.log('Hello World!'); 4 | -------------------------------------------------------------------------------- /example/error/plugins/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "./src", 4 | "baseUrl": "./src" 5 | } 6 | } -------------------------------------------------------------------------------- /example/error/src/index.ts: -------------------------------------------------------------------------------- 1 | import { initMiniStar } from 'mini-star'; 2 | 3 | console.log('App start'); 4 | 5 | initMiniStar({ 6 | plugins: [ 7 | { 8 | name: 'test', 9 | url: '/plugins/test/index.js', 10 | }, 11 | ], 12 | }) 13 | .then(() => { 14 | console.log('Plugin Load Success, Load App'); 15 | }) 16 | .catch(() => { 17 | console.log('Plugin Load Failed'); 18 | }); 19 | -------------------------------------------------------------------------------- /example/error/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "target": "ES6", 5 | "allowJs": false, 6 | "strict": true, 7 | "moduleResolution": "node", 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/fallback/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-fallback", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "", 6 | "main": "index.js", 7 | "scripts": { 8 | "build": "webpack --config ../webpack.config.js --entry ./src/index.ts --output-path ./dist", 9 | "build:plugins": "ministar buildPlugin all", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "author": "moonrailgun", 13 | "license": "MIT", 14 | "dependencies": { 15 | "mini-star": "*" 16 | }, 17 | "devDependencies": { 18 | "webpack": "^5.38.1", 19 | "webpack-cli": "^4.7.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /example/fallback/src/index.ts: -------------------------------------------------------------------------------- 1 | import { initMiniStar } from 'mini-star'; 2 | 3 | console.log('App start'); 4 | 5 | initMiniStar({ 6 | plugins: [ 7 | { 8 | name: 'non-exist-plugin', 9 | url: '/plugins/non-exist-plugin/index.js', 10 | }, 11 | ], 12 | }) 13 | .then(() => { 14 | console.log('Plugin Load Completed'); 15 | }) 16 | .catch(() => { 17 | console.warn('This branch should not trigger'); 18 | }); 19 | -------------------------------------------------------------------------------- /example/fallback/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "target": "ES6", 5 | "allowJs": false, 6 | "strict": true, 7 | "moduleResolution": "node", 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/full/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-full", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "", 6 | "main": "index.js", 7 | "scripts": { 8 | "build": "webpack --config ../webpack.config.js --entry ./src/index.ts --output-path ./dist", 9 | "build:plugins": "ministar buildPlugin all", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "author": "moonrailgun", 13 | "license": "MIT", 14 | "dependencies": { 15 | "console-log-html": "^2.0.2", 16 | "mini-star": "*" 17 | }, 18 | "devDependencies": { 19 | "webpack": "^5.38.1", 20 | "webpack-cli": "^4.7.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /example/full/plugins/alert/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plugins/alert", 3 | "main": "src/index.ts", 4 | "version": "0.0.0", 5 | "dependencies": { 6 | "sweetalert2": "^10.16.6" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /example/full/plugins/alert/src/index.ts: -------------------------------------------------------------------------------- 1 | // import cowsay from 'cowsay-browser'; 2 | import Swal from 'sweetalert2'; 3 | 4 | export function alert(text: string) { 5 | Swal.fire(text); 6 | } 7 | -------------------------------------------------------------------------------- /example/full/plugins/alert/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./src", 5 | "paths": { 6 | "@plugins/*": ["../../../plugins/*"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/full/plugins/alert/typings/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'cowsay'; 2 | -------------------------------------------------------------------------------- /example/full/plugins/alert/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | sweetalert2@^10.16.6: 6 | version "10.16.6" 7 | resolved "https://registry.npmjs.org/sweetalert2/-/sweetalert2-10.16.6.tgz#bf9893386aa9330a22fde188c25897a98559dc4a" 8 | integrity sha512-089SRfG8NopJAVG9euo+kKHWRaCK6V5WY6v1kySYL07SDF/FEEW1Fu+LqkHqvQ0h3KnQ8AE2Z81mpvhMOlx/Vw== 9 | -------------------------------------------------------------------------------- /example/full/plugins/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plugins/core", 3 | "main": "src/index.ts" 4 | } 5 | -------------------------------------------------------------------------------- /example/full/plugins/core/src/async.ts: -------------------------------------------------------------------------------- 1 | export function runB() { 2 | console.log('[core]', 'B is Run[async]'); 3 | } 4 | -------------------------------------------------------------------------------- /example/full/plugins/core/src/index.ts: -------------------------------------------------------------------------------- 1 | import { runA } from './sync'; 2 | import { sharedConsole } from '@capital/shared'; 3 | 4 | runA(); 5 | 6 | import('./async').then((module) => module.runB()); 7 | 8 | sharedConsole('test'); 9 | -------------------------------------------------------------------------------- /example/full/plugins/core/src/sync.ts: -------------------------------------------------------------------------------- 1 | export function runA() { 2 | console.log('[core]', 'A is Run[sync]'); 3 | } 4 | -------------------------------------------------------------------------------- /example/full/plugins/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./src", 5 | "paths": { 6 | "@plugins/*": ["../../../plugins/*"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/full/plugins/core/typings/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@capital/shared'; 2 | -------------------------------------------------------------------------------- /example/full/plugins/test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plugins/test", 3 | "main": "src/index.ts", 4 | "version": "0.0.0", 5 | "dependencies": {} 6 | } 7 | -------------------------------------------------------------------------------- /example/full/plugins/test/src/index.ts: -------------------------------------------------------------------------------- 1 | import { alert as myAlert } from '@plugins/alert'; 2 | 3 | console.log('myAlert', myAlert); 4 | myAlert('Test Alert'); 5 | -------------------------------------------------------------------------------- /example/full/plugins/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./src", 5 | "paths": { 6 | "@plugins/*": ["../../../plugins/*"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/full/plugins/test/typings/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'cowsay'; 2 | -------------------------------------------------------------------------------- /example/full/src/console-ui.ts: -------------------------------------------------------------------------------- 1 | import ConsoleLogHTML from 'console-log-html'; 2 | 3 | const container = document.createElement('ul'); 4 | container.className = 'console-log'; 5 | document.body.appendChild(container); 6 | ConsoleLogHTML.connect(container); // Redirect log messages 7 | // ConsoleLogHTML.disconnect(); // Stop redirecting 8 | -------------------------------------------------------------------------------- /example/full/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'console-log-html'; 2 | -------------------------------------------------------------------------------- /example/full/src/index.css: -------------------------------------------------------------------------------- 1 | .console-log > .text-danger { 2 | color: red; 3 | } 4 | 5 | .console-log > .text-info { 6 | color: grey; 7 | } 8 | -------------------------------------------------------------------------------- /example/full/src/index.ts: -------------------------------------------------------------------------------- 1 | import { appendToolButton } from './toolbar'; 2 | import './console-ui'; 3 | import { initMiniStar, regSharedModule, loadSinglePlugin } from 'mini-star'; 4 | import './index.css'; 5 | 6 | const pluginList = [ 7 | // Plugin List Below 8 | 'core', 9 | ].map((name) => ({ 10 | name, 11 | url: `/plugins/${name}/index.js?v=1`, 12 | })); 13 | 14 | regSharedModule('@capital/shared', () => import('./shared')); 15 | 16 | initMiniStar({ 17 | plugins: pluginList, 18 | }); 19 | appendToolButton('Load plugin: test', () => { 20 | loadSinglePlugin({ 21 | name: 'test', 22 | url: `/plugins/test/index.js`, 23 | }); 24 | }); 25 | appendToolButton('Print loadedModules', () => { 26 | console.log('loadedModules:', (window as any).__ministar_loadedModules); 27 | }); 28 | appendToolButton('Refresh Page', () => { 29 | window.location.reload(); 30 | }); 31 | -------------------------------------------------------------------------------- /example/full/src/shared.ts: -------------------------------------------------------------------------------- 1 | export function sharedConsole(message: string) { 2 | console.log('[shared console]:', message); 3 | } 4 | -------------------------------------------------------------------------------- /example/full/src/toolbar.ts: -------------------------------------------------------------------------------- 1 | const toolbarEl = document.createElement('div'); 2 | document.body.appendChild(toolbarEl); 3 | 4 | export function appendToolButton(text: string, callback: () => void) { 5 | const btn = document.createElement('button'); 6 | btn.textContent = text; 7 | btn.addEventListener('click', callback); 8 | toolbarEl.appendChild(btn); 9 | } 10 | -------------------------------------------------------------------------------- /example/full/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "target": "ES6", 5 | "allowJs": false, 6 | "strict": true, 7 | "moduleResolution": "node", 8 | "baseUrl": ".", 9 | "paths": { 10 | "@capital/*": ["./src/*"], 11 | "@plugins/*": ["./plugins/*"] 12 | }, 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example/simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example-simple", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "", 6 | "main": "index.js", 7 | "scripts": { 8 | "build": "webpack --config ../webpack.config.js --entry ./src/index.ts --output-path ./dist", 9 | "build:plugins": "ministar buildPlugin all", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "author": "moonrailgun", 13 | "license": "MIT", 14 | "dependencies": { 15 | "mini-star": "*" 16 | }, 17 | "devDependencies": { 18 | "webpack": "^5.38.1", 19 | "webpack-cli": "^4.7.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /example/simple/plugins/test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plugins/test", 3 | "main": "src/index.ts", 4 | "version": "0.0.0", 5 | "private": true, 6 | "dependencies": {} 7 | } -------------------------------------------------------------------------------- /example/simple/plugins/test/src/index.ts: -------------------------------------------------------------------------------- 1 | console.log('Hello World!'); 2 | -------------------------------------------------------------------------------- /example/simple/plugins/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "./src", 4 | "baseUrl": "./src" 5 | } 6 | } -------------------------------------------------------------------------------- /example/simple/src/index.ts: -------------------------------------------------------------------------------- 1 | import { initMiniStar } from 'mini-star'; 2 | 3 | console.log('App start'); 4 | 5 | initMiniStar({ 6 | plugins: [ 7 | { 8 | name: 'test', 9 | url: '/plugins/test/index.js', 10 | }, 11 | ], 12 | }).then(() => { 13 | console.log('Plugin Load Success'); 14 | }); 15 | -------------------------------------------------------------------------------- /example/simple/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "ESNext", 4 | "target": "ES6", 5 | "allowJs": false, 6 | "strict": true, 7 | "moduleResolution": "node", 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/vue3/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | public/plugins 7 | -------------------------------------------------------------------------------- /example/vue3/.ministarrc.js: -------------------------------------------------------------------------------- 1 | const vuePlugin = require('rollup-plugin-vue'); 2 | 3 | module.exports = { 4 | outDir: './public', 5 | externalDeps: ['vue'], 6 | buildRollupPlugins: (plugins) => [ 7 | vuePlugin({ 8 | css: true, 9 | compileTemplate: true, 10 | }), 11 | ...plugins, 12 | ], 13 | }; 14 | -------------------------------------------------------------------------------- /example/vue3/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /example/vue3/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/vue3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "build:plugins": "ministar buildPlugin all", 8 | "serve": "vite preview" 9 | }, 10 | "dependencies": { 11 | "mini-star": "*", 12 | "vue": "^3.2.6" 13 | }, 14 | "devDependencies": { 15 | "@vitejs/plugin-vue": "^1.6.0", 16 | "@vue/compiler-sfc": "^3.2.6", 17 | "rollup-plugin-vue": "^6.0.0", 18 | "vite": "^2.5.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /example/vue3/plugins/test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plugins/test", 3 | "main": "src/index.js", 4 | "version": "0.0.0", 5 | "private": true, 6 | "dependencies": {} 7 | } -------------------------------------------------------------------------------- /example/vue3/plugins/test/src/Component.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /example/vue3/plugins/test/src/index.js: -------------------------------------------------------------------------------- 1 | import { regPluginComponent } from '@capital/reg'; 2 | import Component from './Component.vue'; 3 | 4 | regPluginComponent(Component) 5 | 6 | console.log('Hello MiniStar Plugin World!'); 7 | -------------------------------------------------------------------------------- /example/vue3/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonrailgun/mini-star/642e992dd6ad49b7b2441a0f9eed7ceb7fa5e793/example/vue3/public/favicon.ico -------------------------------------------------------------------------------- /example/vue3/src/App.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 15 | 16 | 26 | -------------------------------------------------------------------------------- /example/vue3/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonrailgun/mini-star/642e992dd6ad49b7b2441a0f9eed7ceb7fa5e793/example/vue3/src/assets/logo.png -------------------------------------------------------------------------------- /example/vue3/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 35 | 36 | 41 | -------------------------------------------------------------------------------- /example/vue3/src/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | import {initMiniStar, regSharedModule, regDependency} from '../../../mini-star/runtime/index'; // NOTICE: This is becase of vite wanna have a pure module with bundle 4 | 5 | regDependency('vue', () => import('vue')) 6 | regSharedModule('@capital/reg', () => import('./reg')) 7 | initMiniStar({ 8 | plugins: [ 9 | { 10 | name: 'test', 11 | url: '/plugins/test/index.js', 12 | }, 13 | ], 14 | }).then(() => { 15 | console.log('Plugin Load Success'); 16 | createApp(App).mount('#app'); 17 | }); 18 | -------------------------------------------------------------------------------- /example/vue3/src/reg.js: -------------------------------------------------------------------------------- 1 | let _pluginComponent = null; 2 | 3 | export function regPluginComponent(component) { 4 | _pluginComponent = component; 5 | } 6 | 7 | export function getPluginComponent() { 8 | return _pluginComponent; 9 | } 10 | -------------------------------------------------------------------------------- /example/vue3/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | build: { 8 | target: 'esnext' 9 | } 10 | }) 11 | -------------------------------------------------------------------------------- /example/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const path = require('path'); 3 | const HtmlwebpackPlugin = require('html-webpack-plugin'); 4 | 5 | module.exports = { 6 | mode: 'development', 7 | entry: './capital/src/index.ts', 8 | devtool: 'cheap-module-source-map', // for debug 9 | output: { 10 | filename: 'bundle.js', 11 | path: path.resolve(process.cwd(), 'dist'), 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.css$/, 17 | use: [ 18 | // For css 19 | { loader: 'style-loader' }, 20 | { loader: 'css-loader' }, 21 | ], 22 | exclude: /node_modules/, 23 | }, 24 | { 25 | test: /\.tsx?$/, 26 | exclude: /node_modules/, 27 | loader: 'esbuild-loader', 28 | options: { 29 | loader: 'tsx', // Or 'ts' if you don't need tsx 30 | target: 'es2015', 31 | }, 32 | }, 33 | ], 34 | }, 35 | resolve: { 36 | extensions: ['.js', '.tsx', '.ts'], 37 | }, 38 | plugins: [ 39 | new HtmlwebpackPlugin({ 40 | title: 'Plugin Template', 41 | inject: true, 42 | hash: true, 43 | }), 44 | ], 45 | }; 46 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'jsdom', 4 | globals: { 5 | 'ts-jest': { 6 | tsconfig: 'tsconfig.test.json', 7 | }, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /mini-star/.gitignore: -------------------------------------------------------------------------------- 1 | lib/ 2 | -------------------------------------------------------------------------------- /mini-star/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /mini-star/README.md: -------------------------------------------------------------------------------- 1 | ## mini-star 2 | 3 | A bundler and loader which help your project pluginize. 4 | 5 | [Offical Document](https://ministar.moonrailgun.com/) 6 | 7 | ## Feature 8 | 9 | - Support `Typescript` 10 | - Fast and smart 11 | 12 | ### Different with requirejs 13 | 14 | - Provide a bundler help to build your plugin with `amd`. 15 | - `ministar` will process plugin path and dependencies path. your can write code whatever you like. 16 | - Dynamic load async module, and reduce your project. 17 | - Allow to share dependencies with all plugin. 18 | - Modernize and typescript. 19 | 20 | 21 | ### Deploy to npm 22 | 23 | ```bash 24 | npm run release # not yarn 25 | ``` 26 | -------------------------------------------------------------------------------- /mini-star/build/replace-browser-modules.ts: -------------------------------------------------------------------------------- 1 | import { dirname, join, resolve } from 'node:path'; 2 | import type { Plugin } from 'rollup'; 3 | 4 | const resolutions = { 5 | [resolve('src/utils/crypto')]: resolve('browser/src/crypto.ts'), 6 | [resolve('src/utils/fs')]: resolve('browser/src/fs.ts'), 7 | [resolve('src/utils/hookActions')]: resolve('browser/src/hookActions.ts'), 8 | [resolve('src/utils/path')]: resolve('browser/src/path.ts'), 9 | [resolve('src/utils/performance')]: resolve('browser/src/performance.ts'), 10 | [resolve('src/utils/process')]: resolve('browser/src/process.ts'), 11 | [resolve('src/utils/resolveId')]: resolve('browser/src/resolveId.ts'), 12 | }; 13 | 14 | export default function replaceBrowserModules(): Plugin { 15 | return { 16 | name: 'replace-browser-modules', 17 | resolveId(source, importee) { 18 | if (source.includes('path') || importee?.includes('path')) { 19 | console.log({ source, importee }); 20 | } 21 | 22 | if (importee && source[0] === '.') { 23 | const resolved = join(dirname(importee), source); 24 | 25 | if (resolutions[resolved]) { 26 | return resolutions[resolved]; 27 | } 28 | } 29 | }, 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /mini-star/bundler/browser/README.md: -------------------------------------------------------------------------------- 1 | # Here is MiniStar for browser 2 | -------------------------------------------------------------------------------- /mini-star/bundler/browser/entry.ts: -------------------------------------------------------------------------------- 1 | // import { rollup } from '@rollup/browser'; 2 | import { rollup } from 'rollup'; 3 | import { buildRollupOptions, PluginContext } from '../rollup.config'; 4 | 5 | export async function buildPluginBrowser(pluginContext: PluginContext) { 6 | const options = buildRollupOptions(pluginContext) as any; 7 | // Create a bundle 8 | const bundle = await rollup(options); 9 | 10 | // Write the bundle to disk 11 | if (Array.isArray(options.output)) { 12 | for (const output of options.output) { 13 | await bundle.write(output); 14 | } 15 | } else { 16 | await bundle.write(options.output); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mini-star/bundler/bundle.ts: -------------------------------------------------------------------------------- 1 | import { rollup, RollupWatcher, watch } from 'rollup'; 2 | import { buildRollupOptions, PluginContext } from './rollup.config'; 3 | import fs from 'fs'; 4 | 5 | export async function buildPlugin(pluginPackageJsonPath: string) { 6 | const options = buildRollupOptions(readPackageJSON(pluginPackageJsonPath)); 7 | // Create a bundle 8 | const bundle = await rollup(options); 9 | 10 | // Write the bundle to disk 11 | if (Array.isArray(options.output)) { 12 | for (const output of options.output) { 13 | await bundle.write(output); 14 | } 15 | } else { 16 | await bundle.write(options.output); 17 | } 18 | } 19 | 20 | export function watchPlugin(pluginPackageJsonPath: string): RollupWatcher { 21 | const options = buildRollupOptions(readPackageJSON(pluginPackageJsonPath)); 22 | 23 | const watcher = watch(options); 24 | 25 | return watcher; 26 | } 27 | 28 | /** 29 | * Read package json 30 | */ 31 | function readPackageJSON(pluginPackageJsonPath: string): PluginContext { 32 | let packageConfig: any = {}; 33 | try { 34 | packageConfig = 35 | JSON.parse(fs.readFileSync(pluginPackageJsonPath, 'utf8')) || {}; 36 | } catch (err) { 37 | throw new Error(`Read [${pluginPackageJsonPath}] fail: ${String(err)}`); 38 | } 39 | 40 | if (!('name' in packageConfig) || !('main' in packageConfig)) { 41 | throw new Error(`[${pluginPackageJsonPath}] require field: name, main`); 42 | } 43 | 44 | return { 45 | pluginPackageJsonPath, 46 | packageConfig, 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /mini-star/bundler/config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { cosmiconfigSync } from 'cosmiconfig'; 3 | import { Plugin as RollupPlugin } from 'rollup'; 4 | import { CustomPluginContext } from './types'; 5 | const explorer = cosmiconfigSync('ministar'); 6 | 7 | interface MiniStarConfig { 8 | scope: string; 9 | pluginRoot: string; 10 | outDir: string; 11 | externalDeps: string[]; 12 | /** 13 | * Generate Sourcemap 14 | * @default true 15 | */ 16 | sourceMap?: boolean; 17 | author?: string; 18 | license?: string; 19 | rollupPlugins: 20 | | RollupPlugin[] 21 | | ((context: CustomPluginContext) => RollupPlugin[]); 22 | buildRollupPlugins?: ( 23 | defaultPlugins: RollupPlugin[], 24 | context: CustomPluginContext 25 | ) => RollupPlugin[]; 26 | } 27 | 28 | const defaultConfig: MiniStarConfig = { 29 | scope: 'plugins', 30 | pluginRoot: process.cwd(), 31 | outDir: path.resolve(process.cwd(), './dist'), 32 | externalDeps: [], 33 | author: undefined, 34 | license: undefined, 35 | rollupPlugins: [], 36 | buildRollupPlugins: undefined, 37 | }; 38 | 39 | const configResult = explorer.search(); 40 | const config: MiniStarConfig = { 41 | ...defaultConfig, 42 | ...configResult?.config, 43 | }; 44 | 45 | export { config }; 46 | -------------------------------------------------------------------------------- /mini-star/bundler/rollup.config.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import resolve from '@rollup/plugin-node-resolve'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | import { config } from './config'; 5 | import type { RollupOptions, Plugin as RollupPlugin } from 'rollup'; 6 | import { getPluginDirs } from './utils'; 7 | import styles from 'rollup-plugin-styles'; 8 | import url from '@rollup/plugin-url'; 9 | import esbuild from 'rollup-plugin-esbuild'; 10 | import json from '@rollup/plugin-json'; 11 | import { CustomPluginContext } from './types'; 12 | 13 | // https://github.com/rollup/rollup/blob/master/docs/999-big-list-of-options.md 14 | 15 | type RequiredPick = Omit & Required>; 16 | 17 | export type PluginContext = { 18 | pluginPackageJsonPath: string; 19 | packageConfig: any; 20 | }; 21 | 22 | export function buildRollupOptions( 23 | context: PluginContext 24 | ): RequiredPick { 25 | const { pluginPackageJsonPath, packageConfig } = context; 26 | 27 | const scope = config.scope; 28 | const name = packageConfig.name.replace(`@${scope}/`, ''); 29 | const main = packageConfig.main; 30 | const sourceMap = config.sourceMap ?? true; 31 | 32 | const prefixDepsMatch = [ 33 | ...getPluginDirs().map((x) => `@plugins/${x}`), 34 | '@capital/', // builtin dependencies prefix 35 | ]; 36 | const exactDepsMatch = [...config.externalDeps]; 37 | 38 | const customPluginContext: CustomPluginContext = { 39 | pluginName: name, 40 | }; 41 | 42 | let customRollupPlugins: RollupPlugin[] = []; 43 | if (Array.isArray(config.rollupPlugins)) { 44 | customRollupPlugins = config.rollupPlugins; 45 | } else if (typeof config.rollupPlugins === 'function') { 46 | customRollupPlugins = config.rollupPlugins(customPluginContext); 47 | } 48 | 49 | let plugins: RollupPlugin[] = [ 50 | esbuild({ 51 | // https://www.npmjs.com/package/rollup-plugin-esbuild 52 | // All options are optional 53 | include: /\.[jt]sx?$/, // default, inferred from `loaders` option 54 | exclude: /node_modules/, // default 55 | sourceMap: sourceMap, // default is true 56 | minify: process.env.NODE_ENV === 'production', 57 | tsconfig: path.resolve( 58 | path.dirname(pluginPackageJsonPath), 59 | './tsconfig.json' 60 | ), 61 | }), 62 | styles(), 63 | url(), 64 | json(), 65 | ...customRollupPlugins, 66 | resolve({ browser: true }), 67 | commonjs(), 68 | replaceId(), 69 | ]; 70 | if (typeof config.buildRollupPlugins === 'function') { 71 | plugins = config.buildRollupPlugins(plugins, customPluginContext); 72 | } 73 | 74 | function getFileNameWithoutExt(filepath: string) { 75 | return path.basename(filepath).split('.')[0]; 76 | } 77 | 78 | /** 79 | * reset AMD id to uniq with `pluginName/fileName` 80 | */ 81 | function replaceId(): RollupPlugin { 82 | return { 83 | name: 'replace', 84 | generateBundle(options, bundle) { 85 | Object.values(bundle).forEach((item) => { 86 | if ('code' in item) { 87 | // Process OutputChunk 88 | item.code = item.code.replace( 89 | /definePlugin\(['|"]([^'"]+)['|"],/, 90 | function (match: string, p1: string) { 91 | let moduleId = p1; 92 | if ( 93 | getFileNameWithoutExt(main) === 94 | getFileNameWithoutExt(item.fileName) 95 | ) { 96 | moduleId = `@plugins/${name}`; 97 | } else if (!p1.endsWith('.js')) { 98 | // TODO: Need to optimize 99 | moduleId += '.js'; 100 | } 101 | 102 | return `definePlugin('${moduleId}',`; 103 | } 104 | ); 105 | } 106 | }); 107 | }, 108 | }; 109 | } 110 | 111 | return { 112 | input: path.resolve(path.dirname(pluginPackageJsonPath), main), 113 | output: { 114 | dir: path.resolve(config.outDir, './plugins', name), 115 | format: 'amd', 116 | amd: { 117 | autoId: true, 118 | basePath: `@plugins/${name}`, 119 | define: 'definePlugin', 120 | }, 121 | sourcemap: sourceMap, 122 | }, 123 | plugins, 124 | external: (id: string) => { 125 | for (const dep of prefixDepsMatch) { 126 | if (id.startsWith(dep)) { 127 | return true; 128 | } 129 | } 130 | 131 | for (const dep of exactDepsMatch) { 132 | if (id === dep) { 133 | return true; 134 | } 135 | } 136 | 137 | return false; 138 | }, 139 | preserveEntrySignatures: 'strict', 140 | }; 141 | } 142 | -------------------------------------------------------------------------------- /mini-star/bundler/types.ts: -------------------------------------------------------------------------------- 1 | export interface CustomPluginContext { 2 | pluginName: string; 3 | } 4 | -------------------------------------------------------------------------------- /mini-star/bundler/utils.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { config } from './config'; 4 | 5 | export function getPluginDirContainerPath() { 6 | return path.resolve(config.pluginRoot, './plugins/'); 7 | } 8 | 9 | /** 10 | * Find plugin dir names 11 | */ 12 | export function getPluginDirs(): string[] { 13 | const list = fs.readdirSync(getPluginDirContainerPath()); 14 | 15 | const plugins = list.filter((item) => 16 | fs.statSync(path.resolve(getPluginDirContainerPath(), item)).isDirectory() 17 | ); 18 | 19 | return plugins; 20 | } 21 | -------------------------------------------------------------------------------- /mini-star/cli/create-plugin.ts: -------------------------------------------------------------------------------- 1 | import inquirer from 'inquirer'; 2 | import path from 'path'; 3 | import fs from 'fs-extra'; 4 | import { config } from '../bundler/config'; 5 | 6 | const pluginDir = path.resolve(config.pluginRoot, './plugins'); 7 | fs.ensureDirSync(pluginDir); 8 | 9 | export async function createPluginTemplate() { 10 | const { pluginName, language, confirm } = await inquirer.prompt([ 11 | { 12 | type: 'input', 13 | name: 'pluginName', 14 | message: 'Plugin Name', 15 | validate(input) { 16 | if (!input) { 17 | return 'Require Plugin Name!'; 18 | } 19 | 20 | return true; 21 | }, 22 | }, 23 | { 24 | type: 'list', 25 | name: 'language', 26 | message: 'Language', 27 | choices: [ 28 | { 29 | name: 'Typescript', 30 | value: 'ts', 31 | }, 32 | { 33 | name: 'Javascript', 34 | value: 'js', 35 | }, 36 | ], 37 | }, 38 | { 39 | type: 'confirm', 40 | name: 'confirm', 41 | message: (answer) => { 42 | return `Ensure to create plugin: [${answer.pluginName}] ?`; 43 | }, 44 | }, 45 | ]); 46 | 47 | if (!confirm) { 48 | return; 49 | } 50 | 51 | const entryFileName = `src/index.${language}`; 52 | 53 | const prefix = `@${config.scope}/`; 54 | const uniqPluginName = prefix + pluginName; 55 | 56 | fs.mkdirSync(path.resolve(pluginDir, pluginName)); 57 | fs.mkdirSync(path.resolve(pluginDir, pluginName, 'src')); 58 | 59 | const packageConfig: any = { 60 | name: uniqPluginName, 61 | main: entryFileName, 62 | version: '0.0.0', 63 | private: true, 64 | // scripts: { 65 | // build: 'rollup --config ../rollup.config.js', 66 | // }, 67 | dependencies: {}, 68 | }; 69 | if (config.author !== undefined) { 70 | packageConfig.author = config.author; 71 | } 72 | if (config.license !== undefined) { 73 | packageConfig.license = config.license; 74 | } 75 | 76 | fs.writeFileSync( 77 | path.resolve(pluginDir, pluginName, 'package.json'), 78 | JSON.stringify(packageConfig, null, 2) 79 | ); 80 | fs.writeFileSync( 81 | path.resolve(pluginDir, pluginName, entryFileName), 82 | 'console.log("Hello World!")' 83 | ); 84 | 85 | if (language === 'ts') { 86 | fs.writeFileSync( 87 | path.resolve(pluginDir, pluginName, 'tsconfig.json'), 88 | JSON.stringify( 89 | { 90 | extends: '../../tsconfig.json', 91 | compilerOptions: { 92 | rootDir: './src', 93 | }, 94 | }, 95 | null, 96 | 2 97 | ) 98 | ); 99 | } 100 | 101 | console.log( 102 | `Plugin [${pluginName}] create completed: ${path.resolve( 103 | pluginDir, 104 | pluginName, 105 | entryFileName 106 | )}` 107 | ); 108 | } 109 | -------------------------------------------------------------------------------- /mini-star/cli/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import yargs from 'yargs/yargs'; 4 | import { hideBin } from 'yargs/helpers'; 5 | import { createPluginTemplate } from './create-plugin'; 6 | import { buildPlugin, watchPlugin } from '../bundler/bundle'; 7 | import { getPluginDirContainerPath, getPluginDirs } from '../bundler/utils'; 8 | import ora from 'ora'; 9 | import { getPluginPackagePath, getShortTimeStr } from './utils'; 10 | 11 | yargs(hideBin(process.argv)) 12 | .command( 13 | 'createPlugin', 14 | 'Create a new Plugin', 15 | (yargs) => { 16 | yargs; 17 | // .option('name', { 18 | // describe: 'Plugin Name', 19 | // }) 20 | }, 21 | () => { 22 | createPluginTemplate(); 23 | } 24 | ) 25 | .command( 26 | 'buildPlugin ', 27 | 'Bundle Plugin', 28 | () => {}, 29 | async (argv) => { 30 | const pluginName = argv['pluginName'] as string; 31 | 32 | async function bundleSinglePlugin(name: string) { 33 | const pluginPackageJsonPath = getPluginPackagePath(name); 34 | const spinner = ora(`Building plugin [${name}]...`).start(); 35 | const startTime = new Date().valueOf(); 36 | 37 | try { 38 | await buildPlugin(pluginPackageJsonPath); 39 | const usage = new Date().valueOf() - startTime; 40 | spinner.succeed(`Bundle [${name}] success in ${usage}ms!`); 41 | } catch (err) { 42 | spinner.fail(`Bundle [${name}] error`); 43 | console.error(err); 44 | } 45 | } 46 | 47 | if (pluginName === 'all') { 48 | const allPluginDirs = getPluginDirs(); 49 | if (allPluginDirs.length === 0) { 50 | console.warn('Not found and plugin in:', getPluginDirContainerPath()); 51 | } 52 | 53 | for (const name of getPluginDirs()) { 54 | await bundleSinglePlugin(name); 55 | } 56 | } else { 57 | await bundleSinglePlugin(pluginName); 58 | } 59 | } 60 | ) 61 | .command( 62 | 'watchPlugin ', 63 | 'Build and watch Plugin', 64 | () => {}, 65 | async (argv) => { 66 | const pluginName = argv['pluginName'] as string; 67 | 68 | async function watchSinglePlugin(name: string) { 69 | const pluginPackageJsonPath = getPluginPackagePath(name); 70 | 71 | const watcher = watchPlugin(pluginPackageJsonPath); 72 | 73 | const spinner = ora({ 74 | prefixText: () => `[${getShortTimeStr()}]: [${name}] `, 75 | }); 76 | watcher.on('event', (event) => { 77 | if (event.code === 'BUNDLE_START') { 78 | spinner.start(`Building ${event.output.join(',')}`); 79 | } else if (event.code === 'BUNDLE_END') { 80 | spinner.succeed(`Building Successful in ${event.duration}ms`); 81 | } else if (event.code === 'ERROR') { 82 | spinner.fail(`Build Error: ${event.error.message}`); 83 | } 84 | }); 85 | } 86 | 87 | if (pluginName === 'all') { 88 | for (const name of getPluginDirs()) { 89 | await watchSinglePlugin(name); 90 | } 91 | } else { 92 | await watchSinglePlugin(pluginName); 93 | } 94 | } 95 | ) 96 | .demandCommand(1).argv; 97 | -------------------------------------------------------------------------------- /mini-star/cli/utils.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { config } from '../bundler/config'; 3 | 4 | export function getPluginPackagePath(pluginName: string) { 5 | return path.resolve( 6 | config.pluginRoot, 7 | './plugins/', 8 | pluginName, 9 | './package.json' 10 | ); 11 | } 12 | 13 | /** 14 | * Return HH:mm 15 | */ 16 | export function getShortTimeStr(): string { 17 | const now = new Date(); 18 | 19 | return `${String(now.getHours()).padStart(2, '0')}:${String( 20 | now.getMinutes() 21 | ).padStart(2, '0')}`; 22 | } 23 | -------------------------------------------------------------------------------- /mini-star/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'jsdom', 4 | }; 5 | -------------------------------------------------------------------------------- /mini-star/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-star", 3 | "version": "2.1.8", 4 | "description": "", 5 | "main": "./lib/runtime/index.js", 6 | "scripts": { 7 | "dev": "tsc -p ./tsconfig.build.json --watch", 8 | "build": "rimraf ./lib/**/*.d.ts && tsc -p ./tsconfig.build.json", 9 | "build:browser": "rollup --config rollup.browser.ts --configPlugin typescript", 10 | "crlf": "npx crlf --set=LF ./lib/**/*.js", 11 | "prepare": "pnpm build && pnpm crlf", 12 | "release": "npm publish --registry https://registry.npmjs.com/", 13 | "test": "jest" 14 | }, 15 | "bin": { 16 | "ministar": "./lib/cli/index.js", 17 | "mini-star": "./lib/cli/index.js" 18 | }, 19 | "files": [ 20 | "lib/" 21 | ], 22 | "keywords": [ 23 | "micro-service", 24 | "frontend", 25 | "plugin", 26 | "plugins" 27 | ], 28 | "author": "moonrailgun", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/moonrailgun/mini-star/issues" 32 | }, 33 | "homepage": "https://github.com/moonrailgun/mini-star#readme", 34 | "devDependencies": { 35 | "@rollup/browser": "^3.2.5", 36 | "@rollup/plugin-typescript": "^9.0.2", 37 | "@types/fs-extra": "^9.0.11", 38 | "@types/inquirer": "^8.2.1", 39 | "@types/jest": "^26.0.22", 40 | "@types/yargs": "^16.0.1", 41 | "jest": "^26.6.3", 42 | "ts-jest": "^26.5.4", 43 | "tslib": "^2.4.1", 44 | "typescript": "^4.2.4" 45 | }, 46 | "dependencies": { 47 | "@rollup/plugin-commonjs": "^19.0.0", 48 | "@rollup/plugin-json": "^4.1.0", 49 | "@rollup/plugin-node-resolve": "^13.0.0", 50 | "@rollup/plugin-url": "^6.0.0", 51 | "cosmiconfig": "^7.0.0", 52 | "esbuild": "^0.12.9", 53 | "fs-extra": "^9.1.0", 54 | "inquirer": "^8.2.2", 55 | "ora": "^5.4.0", 56 | "rimraf": "^3.0.2", 57 | "rollup": "^2.48.0", 58 | "rollup-plugin-esbuild": "^4.5.0", 59 | "rollup-plugin-styles": "^3.14.1", 60 | "yargs": "^16.2.0" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /mini-star/rollup.browser.ts: -------------------------------------------------------------------------------- 1 | import commonjs from '@rollup/plugin-commonjs'; 2 | import json from '@rollup/plugin-json'; 3 | import nodeResolve from '@rollup/plugin-node-resolve'; 4 | import { RollupOptions, WarningHandlerWithDefault } from 'rollup'; 5 | import typescript from '@rollup/plugin-typescript'; 6 | import replaceBrowserModules from './build/replace-browser-modules'; 7 | 8 | const onwarn: WarningHandlerWithDefault = (warning) => { 9 | // eslint-disable-next-line no-console 10 | console.error( 11 | 'Building Rollup produced warnings that need to be resolved. ' + 12 | 'Please keep in mind that the browser build may never have external dependencies!' 13 | ); 14 | throw Object.assign(new Error(), warning); 15 | }; 16 | 17 | const browserBuilds: RollupOptions = { 18 | input: 'bundler/browser/entry.ts', 19 | onwarn, 20 | output: [ 21 | // { 22 | // // banner: getBanner, 23 | // file: 'browser/dist/mini-star.browser.js', 24 | // format: 'umd', 25 | // name: 'ministar', 26 | // // plugins: [copyTypes('mini-star.browser.d.ts')], 27 | // sourcemap: true, 28 | // }, 29 | { 30 | // banner: getBanner, 31 | file: 'browser/dist/es/mini-star.browser.js', 32 | format: 'es', 33 | // plugins: [emitModulePackageFile()], 34 | }, 35 | ], 36 | plugins: [ 37 | replaceBrowserModules(), 38 | nodeResolve({ browser: true, preferBuiltins: true }), 39 | json(), 40 | commonjs(), 41 | typescript({ 42 | declaration: false, 43 | }), 44 | ], 45 | strictDeprecations: true, 46 | // treeshake, 47 | }; 48 | 49 | export default browserBuilds; 50 | -------------------------------------------------------------------------------- /mini-star/runtime/__tests__/__snapshots__/utils.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`createNewModuleLoader 1`] = ` 4 | Object { 5 | "entryFn": null, 6 | "ins": null, 7 | "resolves": Array [], 8 | "status": "new", 9 | } 10 | `; 11 | -------------------------------------------------------------------------------- /mini-star/runtime/__tests__/utils.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createNewModuleLoader, 3 | isFullUrl, 4 | mergeUrl, 5 | processModulePath, 6 | } from '../utils'; 7 | 8 | test('createNewModuleLoader', () => { 9 | expect(createNewModuleLoader()).toMatchSnapshot(); 10 | }); 11 | 12 | describe('processModulePath', () => { 13 | test.each([ 14 | ['require', 'require'], 15 | ['exports', 'exports'], 16 | ['model', 'model'], 17 | ['model.js', 'model.js'], 18 | ['./model', '@plugins/demo/model.js'], 19 | ['./model.js', '@plugins/demo/model.js'], 20 | ['@plugins/common', '@plugins/common'], 21 | ])('%s => %s', (input, output) => { 22 | expect(processModulePath('@plugins/demo', input)).toBe(output); 23 | }); 24 | 25 | test('deep path', () => { 26 | expect( 27 | processModulePath( 28 | '@plugins/demo/index-353b8484.js', 29 | './index-4a4caf9c.js' 30 | ) 31 | ).toBe('@plugins/demo/index-4a4caf9c.js'); 32 | }); 33 | }); 34 | 35 | describe('isFullUrl', () => { 36 | test.each([ 37 | ['/plugin/foo/js', false], 38 | ['http://127.0.0.1:8080/plugins/core/index.js', true], 39 | ['https://127.0.0.1:8080/plugins/core/index.js', true], 40 | ])('%s => %s', (input, output) => { 41 | expect(isFullUrl(input)).toBe(output); 42 | }); 43 | }); 44 | 45 | describe('mergeUrl', () => { 46 | test.each([ 47 | [ 48 | 'http://127.0.0.1:8080/plugins/core/index.js', 49 | '/plugins/core/foo.js', 50 | 'http://127.0.0.1:8080/plugins/core/foo.js', 51 | ], 52 | [ 53 | 'http://127.0.0.1:8080/public/plugins/core/index.js', 54 | '/plugins/core/foo.js', 55 | 'http://127.0.0.1:8080/public/plugins/core/foo.js', 56 | ], 57 | [ 58 | 'http://127.0.0.1:8080/public/index.js', 59 | '/plugins/core/foo.js', 60 | '/plugins/core/foo.js', 61 | ], 62 | ])('%s + %s => %s', (input1, input2, output) => { 63 | expect(mergeUrl(input1, input2)).toBe(output); 64 | }); 65 | }); 66 | -------------------------------------------------------------------------------- /mini-star/runtime/config.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | GlobalConfig, 3 | Plugin, 4 | PluginLoadError, 5 | PluginModuleError, 6 | } from './types'; 7 | 8 | const _plugins: Record = {}; 9 | let _pluginUrlPrefix = '/plugins/'; 10 | let _pluginUrlBuilder = (pluginName: string) => 11 | `/plugins/${pluginName}/index.js`; 12 | let _onPluginLoadError = (error: PluginLoadError) => { 13 | console.error('[MiniStar] Plugin Loaded Error', error); 14 | }; 15 | let _removeScriptDomOnLoaded = true; 16 | 17 | export function applyConfig(config: GlobalConfig) { 18 | if (typeof config.pluginUrlPrefix === 'string') { 19 | _pluginUrlPrefix = config.pluginUrlPrefix; 20 | } 21 | 22 | if (typeof config.pluginUrlBuilder === 'function') { 23 | _pluginUrlBuilder = config.pluginUrlBuilder; 24 | } 25 | 26 | if (typeof config.onPluginLoadError === 'function') { 27 | _onPluginLoadError = config.onPluginLoadError; 28 | } 29 | 30 | if (typeof config.removeScriptDomOnLoaded === 'boolean') { 31 | _removeScriptDomOnLoaded = config.removeScriptDomOnLoaded; 32 | } 33 | } 34 | 35 | /** 36 | * get all pluginList 37 | */ 38 | export function getPluginList(): Record { 39 | return _plugins; 40 | } 41 | 42 | /** 43 | * get all pluginUrlPrefix 44 | */ 45 | export function getPluginUrlPrefix(): string { 46 | return _pluginUrlPrefix; 47 | } 48 | 49 | export function callPluginLoadError(error: PluginLoadError) { 50 | _onPluginLoadError(error); 51 | } 52 | 53 | export function callModuleLoadError(error: PluginModuleError) { 54 | // reuse same callback 55 | _onPluginLoadError({ 56 | pluginName: error.moduleName, 57 | detail: error.detail, 58 | }); 59 | } 60 | 61 | /** 62 | * Get plugin url builder 63 | * @default 64 | * (pluginName) => `/plugins/${pluginName}/index.js` 65 | */ 66 | export function getFallbackPluginUrl(pluginName: string): string { 67 | return _pluginUrlBuilder(pluginName); 68 | } 69 | 70 | export function getRemoveScriptDomOnLoaded(): boolean { 71 | return _removeScriptDomOnLoaded; 72 | } 73 | -------------------------------------------------------------------------------- /mini-star/runtime/helper.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getPluginList, 3 | getFallbackPluginUrl, 4 | getPluginUrlPrefix, 5 | callModuleLoadError, 6 | getRemoveScriptDomOnLoaded, 7 | } from './config'; 8 | import type { Module, ModuleLoader } from './types'; 9 | import { 10 | getPluginName, 11 | isFullUrl, 12 | isPluginModuleEntry, 13 | mergeUrl, 14 | processModulePath, 15 | } from './utils'; 16 | 17 | export interface LoadedModuleMap { 18 | [key: string]: ModuleLoader; 19 | } 20 | 21 | const loadedModules: LoadedModuleMap = {}; 22 | 23 | if (process.env.NODE_ENV === 'development') { 24 | // Just for debug. 25 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 26 | (window as any).__ministar_loadedModules = loadedModules; 27 | } 28 | 29 | /** 30 | * get all loaded modules 31 | */ 32 | export function getLoadedModules(): LoadedModuleMap { 33 | return loadedModules; 34 | } 35 | 36 | function generateModuleName(scriptUrl: string): string { 37 | const pluginList = getPluginList(); 38 | const searchedPlugin = Object.entries(pluginList).find( 39 | ([, value]) => value.url === scriptUrl 40 | ); 41 | if (searchedPlugin === undefined) { 42 | // Cannot find Plugin 43 | return scriptUrl; 44 | } else { 45 | return searchedPlugin[1].name; 46 | } 47 | } 48 | 49 | /** 50 | * load plugin script with origin script 51 | * its will not cors problem(rather than fetch and eval, but cannot include sandbox or other post process) 52 | */ 53 | function loadPluginByUrl(url: string): Promise { 54 | return new Promise((resolve, reject) => { 55 | const scriptDom = document.createElement('script'); 56 | scriptDom.src = url; 57 | scriptDom.onload = (e) => { 58 | resolve(e); 59 | if (getRemoveScriptDomOnLoaded()) { 60 | document.body.removeChild(scriptDom); 61 | } 62 | }; 63 | scriptDom.onerror = (e) => { 64 | reject(e); 65 | }; 66 | 67 | document.body.appendChild(scriptDom); 68 | }); 69 | } 70 | 71 | /** 72 | * Because we cannot get result of script eval result 73 | * so we use this to try to make sure script eval. 74 | * 75 | * TODO: use sandbox and eval to remove its logic 76 | */ 77 | const interval = [0, 10, 100, 200, 500]; // eval js is every fast. and should be get status on first 78 | function tryToGetModule(moduleName: string): Promise { 79 | let i = 0; 80 | 81 | function loop(): Promise { 82 | if (i > interval.length) { 83 | // Always can not get module 84 | console.error(`Eval Timeout, moduleName: [${moduleName}]`); 85 | return Promise.reject(); 86 | } 87 | 88 | return new Promise((resolve) => { 89 | setTimeout(() => { 90 | if (loadedModules[moduleName]) { 91 | resolve(); 92 | } else { 93 | i++; 94 | resolve(loop()); 95 | } 96 | }, interval[i]); 97 | }); 98 | } 99 | 100 | return loop() 101 | .then(() => true) 102 | .catch(() => false); 103 | } 104 | 105 | const loadingDependency = new Map>(); 106 | 107 | /** 108 | * Load Dependency Module 109 | */ 110 | async function loadDependency(dep: string): Promise { 111 | const moduleName = generateModuleName(dep); 112 | const pluginName = getPluginName(moduleName); 113 | 114 | if (dep.startsWith('@plugins/')) { 115 | dep = dep.replace('@plugins/', getPluginUrlPrefix()); 116 | } 117 | 118 | /** 119 | * Load script with dom 120 | */ 121 | async function loadDependencyScript() { 122 | const pluginInfo = 123 | typeof pluginName === 'string' ? getPluginList()[pluginName] : null; 124 | 125 | if (isPluginModuleEntry(moduleName)) { 126 | // Is Plugin Entry 127 | if (pluginInfo === null) { 128 | throw new Error(`[${moduleName}] Looks like not a valid module name.`); 129 | } 130 | 131 | /** 132 | * Try to load plugin: 133 | * - `pluginInfo` will be `object` | `undefined` 134 | * - if `undefined`, will not found plugin in `getPluginList()` 135 | * - Try to get plugin url in plugin list. 136 | * - If url not define or this plugin is has another dependencies will generate plugin url with function `getFallbackPluginUrl`. 137 | */ 138 | if (pluginInfo === undefined) { 139 | dep = getFallbackPluginUrl(pluginName!); // here must be have value 140 | } else { 141 | dep = pluginInfo.url ?? getFallbackPluginUrl(pluginInfo.name); 142 | } 143 | } else { 144 | // Async module 145 | if (!dep.startsWith('./') && !dep.endsWith('.js')) { 146 | throw new Error( 147 | `[${dep}] Cannot load, please checkout your code in ${moduleName}(${pluginInfo?.url}).` 148 | ); 149 | } 150 | 151 | if (pluginInfo && isFullUrl(pluginInfo.url ?? '')) { 152 | dep = mergeUrl(pluginInfo.url, dep); 153 | } 154 | } 155 | 156 | await loadPluginByUrl(dep); 157 | 158 | return new Promise((resolve, reject) => { 159 | tryToGetModule(moduleName) 160 | .then((has) => { 161 | if (!has) { 162 | reject(new Error(`Cannot load script: ${moduleName}`)); 163 | return; 164 | } else { 165 | resolve(loadedModules[moduleName]._promise); 166 | } 167 | }) 168 | .catch((e) => { 169 | reject(e); 170 | }); 171 | }); 172 | } 173 | 174 | if (loadingDependency.has(moduleName)) { 175 | // Is Loading 176 | return loadingDependency.get(moduleName) as Promise; 177 | } 178 | 179 | if (!(moduleName in loadedModules)) { 180 | // Not load 181 | const p = loadDependencyScript(); 182 | loadingDependency.set(moduleName, p); 183 | return p.then((res) => { 184 | loadingDependency.delete(moduleName); 185 | return res; 186 | }); 187 | } 188 | 189 | // defined 190 | const pluginModule = loadedModules[moduleName]; 191 | return pluginModule._promise; 192 | } 193 | 194 | export function requirePlugin( 195 | deps: string[], 196 | onSuccess: (...args: Module[]) => void, 197 | onError: (err: Error) => void 198 | ): void { 199 | const allPromises = Promise.all( 200 | deps 201 | .map((dep) => { 202 | return loadDependency(dep); 203 | }) 204 | .filter(Boolean) 205 | ); 206 | 207 | allPromises 208 | .then((args) => { 209 | onSuccess(...args); 210 | }) 211 | .catch((err) => { 212 | console.error(err); 213 | onError(err); 214 | }); 215 | } 216 | 217 | export function definePlugin( 218 | name: string, 219 | deps: string[], 220 | callback: (...args: any[]) => Module 221 | ) { 222 | const moduleName = name; 223 | 224 | if (arguments.length === 2) { 225 | // AMD No Deps case 226 | // Should neven run into here because of 227 | callback = deps as any; 228 | deps = []; 229 | } 230 | 231 | if (loadedModules[moduleName]) { 232 | console.warn(`${moduleName} has been loaded. Skipped!`); 233 | return; 234 | } 235 | 236 | loadedModules[moduleName] = { 237 | module: null, 238 | _promise: new Promise((resolve, reject) => { 239 | const convertedDeps = deps.map((module) => 240 | processModulePath(name, module) 241 | ); 242 | 243 | const requireIndex = deps.findIndex((x) => x === 'require'); 244 | const exportsIndex = deps.findIndex((x) => x === 'exports'); 245 | 246 | const requiredDeps = convertedDeps.filter( 247 | (x) => x !== 'require' && x !== 'exports' 248 | ); 249 | 250 | requirePlugin( 251 | requiredDeps, 252 | (...callbackArgs) => { 253 | let exports: Record = {}; 254 | 255 | // Replace require 256 | if (requireIndex !== -1) { 257 | (callbackArgs as any[]).splice( 258 | requireIndex, 259 | 0, 260 | (deps: string[], callback: (...args: any[]) => void) => { 261 | const convertedDeps = deps.map((module) => 262 | processModulePath(name, module) 263 | ); 264 | 265 | requirePlugin(convertedDeps, callback, (err) => 266 | callModuleLoadError({ 267 | moduleName, 268 | detail: err, 269 | }) 270 | ); 271 | } 272 | ); 273 | } 274 | 275 | // Replace exports 276 | if (exportsIndex !== -1) { 277 | callbackArgs.splice(exportsIndex, 0, exports); 278 | } 279 | 280 | try { 281 | const ret = callback(...callbackArgs); 282 | if (exportsIndex === -1 && ret) { 283 | exports = ret; 284 | } 285 | 286 | resolve(exports); 287 | // setModuleLoaderLoaded(loadedModules[name], exports); 288 | } catch (e: any) { 289 | // Run script error 290 | callModuleLoadError({ 291 | moduleName: name, 292 | detail: new Error(e), 293 | }); 294 | reject(e); 295 | // setModuleLoaderLoadError(loadedModules[name]); 296 | } 297 | }, 298 | (err) => { 299 | // Load dependency error 300 | callModuleLoadError({ 301 | moduleName, 302 | detail: err, 303 | }); 304 | reject(err); 305 | } 306 | ); 307 | }).then((exportedModule) => { 308 | loadedModules[moduleName].module = exportedModule; 309 | return exportedModule; 310 | }), 311 | }; 312 | 313 | return loadedModules[moduleName]._promise; 314 | } 315 | 316 | /** 317 | * Register Dependency from node_module. 318 | * make sure declare it in `externalDeps` 319 | */ 320 | export function regDependency(name: string, fn: () => Promise) { 321 | if (loadedModules[name]) { 322 | console.warn('[ministar] Duplicate registry:', name); 323 | } 324 | loadedModules[name] = { 325 | module: null, 326 | _promise: fn().then((exportedModule) => { 327 | loadedModules[name].module = exportedModule; 328 | return exportedModule; 329 | }), 330 | }; 331 | } 332 | 333 | /** 334 | * Register Shared Module from capital. 335 | * Its will auto try to add @capital before module name(if you not declare it). 336 | */ 337 | export function regSharedModule(name: string, fn: () => Promise) { 338 | if ( 339 | !name.startsWith('@capital') && 340 | !name.startsWith('@') && 341 | !name.startsWith('/') 342 | ) { 343 | // Auto Prefix 344 | name = `@capital/${name}`; 345 | } 346 | 347 | regDependency(name, fn); 348 | } 349 | -------------------------------------------------------------------------------- /mini-star/runtime/index.ts: -------------------------------------------------------------------------------- 1 | import './types'; 2 | import './init'; 3 | 4 | export { initMiniStar, loadSinglePlugin, loadPluginList } from './loader'; 5 | export { regDependency, regSharedModule, getLoadedModules } from './helper'; 6 | export type { LoadedModuleMap } from './helper'; 7 | -------------------------------------------------------------------------------- /mini-star/runtime/init.ts: -------------------------------------------------------------------------------- 1 | import { definePlugin, requirePlugin } from './helper'; 2 | import type { Module } from './types'; 3 | 4 | declare global { 5 | interface Window { 6 | definePlugin: ( 7 | name: string, 8 | deps: string[], 9 | callback: (...args: unknown[]) => Module 10 | ) => void; 11 | requirePlugin: ( 12 | deps: string[], 13 | onSuccess: (...args: Module[]) => void, 14 | onError: (err: Error) => void 15 | ) => void; 16 | } 17 | } 18 | 19 | window.requirePlugin = requirePlugin; 20 | 21 | window.definePlugin = definePlugin; 22 | -------------------------------------------------------------------------------- /mini-star/runtime/loader.ts: -------------------------------------------------------------------------------- 1 | import { applyConfig, callPluginLoadError, getPluginList } from './config'; 2 | import { requirePlugin } from './helper'; 3 | import type { GlobalConfig, Module, Plugin } from './types'; 4 | 5 | /** 6 | * Load All Plugin from List 7 | */ 8 | export function loadPluginList(plugins: Plugin[]): Promise { 9 | const allPromise = plugins.map((plugin) => { 10 | // Append Plugins 11 | getPluginList()[plugin.name] = { 12 | ...plugin, 13 | name: `@plugins/${plugin.name}`, 14 | }; 15 | 16 | const pluginName = plugin.name; 17 | const pluginUrl = plugin.url; 18 | 19 | return new Promise((resolve, reject) => { 20 | console.debug(`[${pluginName}] Start Loading...`); 21 | requirePlugin( 22 | [`${pluginUrl}`], 23 | (pluginModule: Module) => { 24 | console.debug(`[${pluginName}] Load Completed!`); 25 | resolve(pluginModule); 26 | }, 27 | (err) => { 28 | reject(err); 29 | } 30 | ); 31 | }).catch((error) => { 32 | callPluginLoadError({ 33 | pluginName, 34 | detail: new Error(error), 35 | }); 36 | return null; 37 | }); 38 | }); 39 | 40 | return Promise.all(allPromise).then( 41 | (modules) => modules.filter(Boolean) as Module[] 42 | ); 43 | } 44 | 45 | /** 46 | * Load Single Plugin 47 | */ 48 | export async function loadSinglePlugin(plugin: Plugin): Promise { 49 | const [pluginModule] = await loadPluginList([plugin]); 50 | return pluginModule; 51 | } 52 | 53 | interface MiniStarOptions extends GlobalConfig { 54 | plugins?: Plugin[]; 55 | } 56 | /** 57 | * Init Mini Star 58 | */ 59 | export async function initMiniStar(options: MiniStarOptions) { 60 | applyConfig(options); // Apply init runtime config 61 | 62 | if (Array.isArray(options.plugins) && options.plugins.length > 0) { 63 | await loadPluginList(options.plugins); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /mini-star/runtime/types.ts: -------------------------------------------------------------------------------- 1 | export type ModuleStatus = 2 | | 'new' // when plugin dep created 3 | | 'init' // when plugin load in first time 4 | | 'loading' // when plugin downloaded and before run entryFn 5 | | 'loaded' // when plugin run entryFn completed 6 | | 'error'; // when plugin load error 7 | 8 | export interface PluginLoadError { 9 | pluginName: string; 10 | detail: Error; 11 | } 12 | 13 | export interface PluginModuleError { 14 | moduleName: string; 15 | detail: Error; 16 | } 17 | 18 | export interface GlobalConfig { 19 | /** 20 | * Whether if remove script dom on loaded 21 | * Useful make dom clean 22 | */ 23 | removeScriptDomOnLoaded?: boolean; 24 | /** 25 | * plugin url prefix, will replace `@plugin/xxx` to `${pluginUrlPrefix}/xxx` 26 | * 27 | * @deprecated 28 | */ 29 | pluginUrlPrefix?: string; 30 | /** 31 | * Build plugin url with function, for import other plugin in plugin 32 | */ 33 | pluginUrlBuilder?: (pluginName: string) => string; 34 | /** 35 | * Plugin load error callback 36 | */ 37 | onPluginLoadError?: (error: PluginLoadError) => void; 38 | } 39 | 40 | export interface Plugin { 41 | /** 42 | * Plugin Name 43 | */ 44 | name: string; 45 | /** 46 | * Plugin Url 47 | */ 48 | url: string; 49 | } 50 | 51 | export interface Module { 52 | default?: unknown; 53 | [key: string]: unknown; 54 | } 55 | 56 | export interface ModuleLoader { 57 | _promise: Promise; 58 | module: Module | null; 59 | } 60 | -------------------------------------------------------------------------------- /mini-star/runtime/utils.ts: -------------------------------------------------------------------------------- 1 | function trimDotOfPath(parent: string, self: string) { 2 | if (!self.startsWith('.')) { 3 | return self; 4 | } 5 | const parentList = parent.split('/'); 6 | const selfList = self.split('/'); 7 | parentList.pop(); 8 | const selfRet: string[] = []; 9 | selfList.forEach((item) => { 10 | if (item === '..') { 11 | parentList.pop(); 12 | } 13 | if (item !== '..' && item !== '.') { 14 | selfRet.push(item); 15 | } 16 | }); 17 | return parentList.concat(selfRet).join('/'); 18 | } 19 | 20 | /** 21 | * A function how to process module path to real path 22 | */ 23 | export function processModulePath( 24 | baseModuleName: string, 25 | path: string 26 | ): string { 27 | if (isPluginModuleName(path)) { 28 | return path; 29 | } 30 | 31 | if (path.endsWith('.js') || path.startsWith('./')) { 32 | return trimDotOfPath( 33 | !baseModuleName.endsWith('.js') ? baseModuleName + '/' : baseModuleName, 34 | !path.endsWith('.js') ? `${path}.js` : path 35 | ); 36 | } 37 | 38 | return path; 39 | } 40 | 41 | /** 42 | * @example 43 | * @plugins/any => true 44 | */ 45 | export function isPluginModuleName(fullName: string): boolean { 46 | if (typeof fullName !== 'string') { 47 | return false; 48 | } 49 | 50 | return fullName.startsWith('@plugins/'); 51 | } 52 | 53 | /** 54 | * @example 55 | * @plugins/any => true 56 | * @plugins/any/other.js => false 57 | */ 58 | export function isPluginModuleEntry(fullName: string): boolean { 59 | if (typeof fullName !== 'string') { 60 | return false; 61 | } 62 | 63 | return fullName.startsWith('@plugins/') && fullName.split('/').length === 2; 64 | } 65 | 66 | /** 67 | * @example 68 | * @plugins/any => false 69 | * @plugins/any/other.js => true 70 | * @plugins/any/dir/another.js => true 71 | */ 72 | export function isPluginSubModule(fullName: string): boolean { 73 | if (typeof fullName !== 'string') { 74 | return false; 75 | } 76 | 77 | return fullName.startsWith('@plugins/') && fullName.split('/').length > 2; 78 | } 79 | 80 | /** 81 | * @example 82 | * @plugins/any => any 83 | */ 84 | export function getPluginName(fullName: string): string | null { 85 | if (!isPluginModuleName(fullName)) { 86 | return null; 87 | } 88 | 89 | const [, name] = fullName.split('/'); 90 | return name; 91 | } 92 | 93 | /** 94 | * Check is full url 95 | */ 96 | export function isFullUrl(url: string): boolean { 97 | return url.startsWith('http:') || url.startsWith('https:'); 98 | } 99 | 100 | /** 101 | * @example 102 | * ('http://127.0.0.1:8080/plugins/core/index.js', '/plugins/core/foo.js') => 'http://127.0.0.1:8080/plugins/core/foo.js' 103 | * ('http://127.0.0.1:8080/public/plugins/core/index.js', '/plugins/core/foo.js') => 'http://127.0.0.1:8080/public/plugins/core/foo.js' 104 | * ('http://127.0.0.1:8080/public/core/index.js', '/plugins/core/foo.js') => 'http://127.0.0.1:8080/public/core/foo.js' 105 | * ('http://127.0.0.1:8080/public/index.js', '/plugins/core/foo.js') => '/plugins/core/foo.js' 106 | */ 107 | export function mergeUrl(baseUrl: string, appendUrl: string) { 108 | const [, prefix, pluginName, ...rest] = appendUrl.split('/'); 109 | const matchStr = `/${pluginName}`; 110 | 111 | const index = baseUrl.search(matchStr); 112 | if (index === -1) { 113 | return appendUrl; 114 | } 115 | 116 | return baseUrl.slice(0, index) + matchStr + '/' + rest.join('/'); 117 | } 118 | -------------------------------------------------------------------------------- /mini-star/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [ 4 | "./bundler/*", 5 | "./cli/*", 6 | "./runtime/*" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /mini-star/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "resolveJsonModule": true, 5 | "noImplicitAny": true, 6 | "declaration": true, 7 | "target": "ES5", 8 | "outDir": "lib", 9 | "allowJs": false, 10 | "strict": true, 11 | "moduleResolution": "node", 12 | "rootDir": ".", 13 | "typeRoots": ["./types", "./node_modules/@types"] 14 | }, 15 | "exclude": ["node_modules", "lib", "**/__tests__/**"] 16 | } 17 | -------------------------------------------------------------------------------- /mini-star/types/rollup__plugin-url/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@rollup/plugin-url'; 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plugin-template", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "private": true, 7 | "scripts": { 8 | "prepare": "husky install", 9 | "plugin:new": "ts-node -P ./mini-star/tsconfig.json mini-star/cli/index.ts createPlugin", 10 | "plugin:build": "rm -rf ./dist/plugins && ts-node -P ./mini-star/tsconfig.json mini-star/cli/index.ts buildPlugin all", 11 | "plugin:watch": "rm -rf ./dist/plugins && ts-node -P ./mini-star/tsconfig.json mini-star/cli/index.ts watchPlugin all", 12 | "lint": "eslint './**/*.{ts,tsx}'", 13 | "lint:fix": "eslint --fix './**/*.{ts,tsx}'", 14 | "test": "jest" 15 | }, 16 | "keywords": [ 17 | "plugin" 18 | ], 19 | "author": "moonrailgun", 20 | "license": "MIT", 21 | "lint-staged": { 22 | "src/*.{json,less}": [ 23 | "prettier --write --config ./.prettierrc.json" 24 | ], 25 | "**/*.js": [ 26 | "prettier --write --config ./.prettierrc.json" 27 | ], 28 | "**/*.{ts,tsx}": [ 29 | "eslint --fix", 30 | "prettier --write --config ./.prettierrc.json" 31 | ] 32 | }, 33 | "devDependencies": { 34 | "@commitlint/cli": "^12.1.1", 35 | "@commitlint/config-conventional": "^12.1.1", 36 | "@types/inquirer": "^8.2.1", 37 | "@types/jest": "^26.0.23", 38 | "@types/jsdom": "^16.2.10", 39 | "@typescript-eslint/eslint-plugin": "^4.22.0", 40 | "@typescript-eslint/parser": "^4.22.0", 41 | "css-loader": "^5.2.4", 42 | "esbuild-loader": "^2.16.0", 43 | "eslint-config-prettier": "^8.3.0", 44 | "eslint-plugin-prettier": "^3.4.0", 45 | "eslint-plugin-react": "^7.23.2", 46 | "glob": "^7.1.6", 47 | "html-webpack-plugin": "^5.3.1", 48 | "husky": "^6.0.0", 49 | "inquirer": "^8.2.2", 50 | "jest": "^26.6.3", 51 | "jsdom": "^16.5.3", 52 | "lint-staged": "^10.5.4", 53 | "prettier": "^2.2.1", 54 | "react": "^17.0.2", 55 | "rollup": "^2.45.1", 56 | "style-loader": "^2.0.0", 57 | "ts-jest": "^26.5.5", 58 | "ts-loader": "^8.1.0", 59 | "ts-node": "^9.1.1", 60 | "typescript": "^4.2.4", 61 | "webpack": "^5.31.2", 62 | "webpack-cli": "^4.6.0", 63 | "webpack-dev-server": "^3.11.2" 64 | }, 65 | "dependencies": { 66 | "eslint": "^7.25.0" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /playground/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | MiniStar Playground 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@monaco-editor/react": "^4.4.6", 13 | "@rollup/browser": "^3.2.5", 14 | "allotment": "^1.17.0", 15 | "mini-star": "workspace:*", 16 | "react": "^18.2.0", 17 | "react-dom": "^18.2.0" 18 | }, 19 | "devDependencies": { 20 | "@types/react": "^18.0.22", 21 | "@types/react-dom": "^18.0.7", 22 | "@vitejs/plugin-react": "^2.2.0", 23 | "autoprefixer": "^10.4.13", 24 | "tailwindcss": "^2.2.4", 25 | "typescript": "^4.6.4", 26 | "vite": "^3.2.0" 27 | } 28 | } -------------------------------------------------------------------------------- /playground/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /playground/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonrailgun/mini-star/642e992dd6ad49b7b2441a0f9eed7ceb7fa5e793/playground/public/favicon.ico -------------------------------------------------------------------------------- /playground/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /playground/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Editor } from './components/Editor'; 2 | import { Previewer } from './components/Previewer'; 3 | import { Allotment } from 'allotment'; 4 | import 'allotment/dist/style.css'; 5 | 6 | import defaultCapitalApp from './defaultFiles/defaultCapitalApp?raw'; 7 | import defaultMiniStarRC from './defaultFiles/defaultMiniStarRC?raw'; 8 | 9 | import * as rollup from '@rollup/browser'; 10 | import { dirname, resolve } from './utils/path'; 11 | import { supportsCodeSplitting, supportsInput } from './utils/version'; 12 | 13 | function App() { 14 | const codeSplitting = supportsCodeSplitting(rollup.VERSION); 15 | const handleCompile = async () => { 16 | // https://github.com/rollup/rollup/issues/3012#issuecomment-517094597 17 | // https://github.com/rollup/rollupjs.org/blob/master/src/stores/rollupOutput.js 18 | 19 | console.clear(); 20 | console.log( 21 | `running Rollup version %c${rollup.VERSION}`, 22 | 'font-weight: bold' 23 | ); 24 | 25 | const modules = [ 26 | { 27 | name: 'main.js', 28 | code: "console.log('Hello World!')", 29 | isEntry: true, 30 | }, 31 | ]; 32 | 33 | const moduleById: Record = {}; 34 | modules.forEach((module) => { 35 | moduleById[module.name] = module; 36 | }); 37 | 38 | const warnings: any[] = []; 39 | const inputOptions: any = { 40 | plugins: [ 41 | { 42 | resolveId(importee: string, importer: string) { 43 | if (!importer) return importee; 44 | if (importee[0] !== '.') return false; 45 | 46 | let resolved = resolve(dirname(importer), importee).replace( 47 | /^\.\//, 48 | '' 49 | ); 50 | if (resolved in moduleById) { 51 | return resolved; 52 | } 53 | 54 | resolved += '.js'; 55 | if (resolved in moduleById) { 56 | return resolved; 57 | } 58 | 59 | throw new Error( 60 | `Could not resolve '${importee}' from '${importer}'` 61 | ); 62 | }, 63 | load: function (id: any) { 64 | return moduleById[id].code; 65 | }, 66 | }, 67 | ], 68 | onwarn(warning: any) { 69 | warnings.push(warning); 70 | 71 | console.group(warning.loc ? warning.loc.file : ''); 72 | 73 | console.warn(warning.message); 74 | 75 | if (warning.frame) { 76 | console.log(warning.frame); 77 | } 78 | 79 | if (warning.url) { 80 | console.log(`See ${warning.url} for more information`); 81 | } 82 | 83 | console.groupEnd(); 84 | }, 85 | }; 86 | if (codeSplitting) { 87 | inputOptions.input = modules 88 | .filter((module, index) => index === 0 || module.isEntry) 89 | .map((module) => module.name); 90 | } else { 91 | inputOptions[supportsInput(rollup.VERSION) ? 'input' : 'entry'] = 92 | 'main.js'; 93 | } 94 | 95 | try { 96 | const generated = await (await rollup.rollup(inputOptions)).generate({}); 97 | 98 | console.log({ 99 | output: supportsCodeSplitting(rollup.VERSION) 100 | ? generated.output 101 | : [generated], 102 | warnings, 103 | error: null, 104 | }); 105 | } catch (err) { 106 | console.error(err); 107 | 108 | console.log({ 109 | output: [], 110 | warnings, 111 | error: null, 112 | }); 113 | } 114 | }; 115 | 116 | return ( 117 |
118 |
119 |
120 | 121 |
MiniStar Playground
122 |
123 | 124 |
128 | Run(WIP) 129 |
130 |
131 | 132 |
133 | 134 | 135 | 136 | 137 | 141 | 142 | 143 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 |
156 |
157 | ); 158 | } 159 | 160 | export default App; 161 | -------------------------------------------------------------------------------- /playground/src/components/Editor.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import MonacoEditor, { EditorProps } from '@monaco-editor/react'; 3 | 4 | export const Editor: React.FC = React.memo( 5 | (props) => { 6 | return ( 7 |
8 | {props.title &&
{props.title}
} 9 | 10 | 11 |
12 | ); 13 | } 14 | ); 15 | Editor.displayName = 'Editor'; 16 | -------------------------------------------------------------------------------- /playground/src/components/Previewer.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const Previewer: React.FC = React.memo(() => { 4 | return
Previewer
; 5 | }); 6 | Previewer.displayName = 'Previewer'; 7 | -------------------------------------------------------------------------------- /playground/src/defaultFiles/defaultCapitalApp.tsx: -------------------------------------------------------------------------------- 1 | import { initMiniStar } from 'mini-star'; 2 | 3 | console.log('App start'); 4 | 5 | initMiniStar({ 6 | plugins: [ 7 | { 8 | name: 'test', 9 | url: '/plugins/test/index.js', 10 | }, 11 | ], 12 | }).then(() => { 13 | console.log('Plugin Load Success'); 14 | }); 15 | -------------------------------------------------------------------------------- /playground/src/defaultFiles/defaultMiniStarRC.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /playground/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 16px; 4 | line-height: 24px; 5 | font-weight: 400; 6 | 7 | width: 100%; 8 | height: 100%; 9 | 10 | color-scheme: light dark; 11 | color: rgba(255, 255, 255, 0.87); 12 | background-color: #242424; 13 | 14 | font-synthesis: none; 15 | text-rendering: optimizeLegibility; 16 | -webkit-font-smoothing: antialiased; 17 | -moz-osx-font-smoothing: grayscale; 18 | -webkit-text-size-adjust: 100%; 19 | } 20 | -------------------------------------------------------------------------------- /playground/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | import './index.css'; 5 | import './tailwind.css'; 6 | 7 | ReactDOM.createRoot(document.getElementById('app') as HTMLElement).render( 8 | 9 | 10 | 11 | ); 12 | -------------------------------------------------------------------------------- /playground/src/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /playground/src/utils/path.ts: -------------------------------------------------------------------------------- 1 | export const absolutePath = /^(?:\/|(?:[A-Za-z]:)?[\\|\/])/; 2 | export const relativePath = /^\.?\.\//; 3 | 4 | export function isAbsolute(path: string) { 5 | return absolutePath.test(path); 6 | } 7 | 8 | export function isRelative(path: string) { 9 | return relativePath.test(path); 10 | } 11 | 12 | export function basename(path: string): string { 13 | return path.split(/(\/|\\)/).pop() ?? ''; 14 | } 15 | 16 | export function dirname(path: string) { 17 | const match = /(\/|\\)[^\/\\]*$/.exec(path); 18 | if (!match) return '.'; 19 | 20 | const dir = path.slice(0, -match[0].length); 21 | 22 | // If `dir` is the empty string, we're at root. 23 | return dir ? dir : '/'; 24 | } 25 | 26 | export function extname(path: string) { 27 | const match = /\.[^\.]+$/.exec(basename(path)); 28 | if (!match) return ''; 29 | return match[0]; 30 | } 31 | 32 | export function relative(from: string, to: string) { 33 | const fromParts = from.split(/[\/\\]/).filter(Boolean); 34 | const toParts = to.split(/[\/\\]/).filter(Boolean); 35 | 36 | while (fromParts[0] && toParts[0] && fromParts[0] === toParts[0]) { 37 | fromParts.shift(); 38 | toParts.shift(); 39 | } 40 | 41 | while (toParts[0] === '.' || toParts[0] === '..') { 42 | const toPart = toParts.shift(); 43 | if (toPart === '..') { 44 | fromParts.pop(); 45 | } 46 | } 47 | 48 | while (fromParts.pop()) { 49 | toParts.unshift('..'); 50 | } 51 | 52 | return toParts.join('/'); 53 | } 54 | 55 | export function resolve(...paths: string[]) { 56 | let resolvedParts = paths.shift().split(/[\/\\]/); 57 | 58 | paths.forEach((path) => { 59 | if (isAbsolute(path)) { 60 | resolvedParts = path.split(/[\/\\]/); 61 | } else { 62 | const parts = path.split(/[\/\\]/); 63 | 64 | while (parts[0] === '.' || parts[0] === '..') { 65 | const part = parts.shift(); 66 | if (part === '..') { 67 | resolvedParts.pop(); 68 | } 69 | } 70 | 71 | resolvedParts.push.apply(resolvedParts, parts); 72 | } 73 | }); 74 | 75 | return resolvedParts.join('/'); 76 | } 77 | -------------------------------------------------------------------------------- /playground/src/utils/version.ts: -------------------------------------------------------------------------------- 1 | const isRollupVersionAtLeast = ( 2 | version: string, 3 | major: number, 4 | minor: number 5 | ) => { 6 | if (!version) return true; 7 | const [currentMajor, currentMinor] = version.split('.').map(Number); 8 | return ( 9 | currentMajor > major || (currentMajor === major && currentMinor >= minor) 10 | ); 11 | }; 12 | 13 | export const supportsInput = (version: string) => 14 | isRollupVersionAtLeast(version, 0, 48); 15 | 16 | export const supportsCodeSplitting = (version: string) => 17 | isRollupVersionAtLeast(version, 1, 0); 18 | -------------------------------------------------------------------------------- /playground/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /playground/tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ['./index.html', './src/**/*.{ts,tsx}'], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /playground/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /playground/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | resolve: { 8 | alias: { 9 | rollup: '@rollup/browser', 10 | }, 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'mini-star' 3 | - 'example/**' 4 | - 'playground' 5 | - 'test/plugins/*' 6 | -------------------------------------------------------------------------------- /test/plugins/async/__snapshots__/async.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`plugin: simple Should build async plugin: /plugins/demo/async-5631a60a.js 1`] = ` 4 | "definePlugin('@plugins/demo/async-5631a60a.js', function () { 'use strict'; 5 | 6 | console.log(\\"load async\\"); 7 | 8 | }); 9 | //# sourceMappingURL=async-5631a60a.js.map 10 | " 11 | `; 12 | 13 | exports[`plugin: simple Should build async plugin: /plugins/demo/async-5631a60a.js.map 1`] = `"{\\"version\\":3,\\"file\\":\\"async-5631a60a.js\\",\\"sources\\":[\\"../../../plugins/demo/src/async.ts\\"],\\"sourcesContent\\":[\\"console.log('load async');\\\\n\\\\nexport {};\\\\n\\"],\\"names\\":[],\\"mappings\\":\\";;CAAA,QAAQ,IAAI;;;;;;\\"}"`; 14 | 15 | exports[`plugin: simple Should build async plugin: /plugins/demo/index.js 1`] = ` 16 | "definePlugin('@plugins/demo', ['require'], function (require) { 'use strict'; 17 | 18 | new Promise(function (resolve, reject) { require(['./async-5631a60a'], resolve, reject) }).then(() => { 19 | console.log(\\"Hello World!\\"); 20 | }); 21 | 22 | }); 23 | //# sourceMappingURL=index.js.map 24 | " 25 | `; 26 | 27 | exports[`plugin: simple Should build async plugin: /plugins/demo/index.js.map 1`] = `"{\\"version\\":3,\\"file\\":\\"index.js\\",\\"sources\\":[\\"../../../plugins/demo/src/index.ts\\"],\\"sourcesContent\\":[\\"import('./async').then(() => {\\\\n console.log('Hello World!');\\\\n});\\\\n\\"],\\"names\\":[],\\"mappings\\":\\";;AAAA,oDAAO,yCAAW,KAAK,MAAM;EAC3B,UAAQ,IAAI;EAAA;;;;;;\\"}"`; 28 | -------------------------------------------------------------------------------- /test/plugins/async/async.spec.ts: -------------------------------------------------------------------------------- 1 | import { readFiles, setupBuildTest } from '../../utils'; 2 | import path from 'path'; 3 | 4 | const output = path.join(__dirname, 'dist'); 5 | 6 | describe('plugin: simple', () => { 7 | let files: Record = {}; 8 | 9 | beforeAll(() => { 10 | setupBuildTest(__dirname); 11 | 12 | files = readFiles(output); 13 | }); 14 | 15 | it('Should build async plugin', () => { 16 | for (const fileName in files) { 17 | expect(files[fileName]).toMatchSnapshot(fileName); 18 | } 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/plugins/async/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mini-star/test-async", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "testbuild": "rm -rf ./dist && ministar buildPlugin all" 7 | }, 8 | "devDependencies": { 9 | "mini-star": "*" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/plugins/async/plugins/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plugins/demo", 3 | "main": "src/index.ts", 4 | "private": true 5 | } 6 | -------------------------------------------------------------------------------- /test/plugins/async/plugins/demo/src/async.ts: -------------------------------------------------------------------------------- 1 | console.log('load async'); 2 | 3 | export {}; 4 | -------------------------------------------------------------------------------- /test/plugins/async/plugins/demo/src/index.ts: -------------------------------------------------------------------------------- 1 | import('./async').then(() => { 2 | console.log('Hello World!'); 3 | }); 4 | -------------------------------------------------------------------------------- /test/plugins/async/plugins/demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./src", 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/plugins/auto-load/__snapshots__/auto-load.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`runtime html 1`] = `""`; 4 | 5 | exports[`runtime html 2`] = `""`; 6 | -------------------------------------------------------------------------------- /test/plugins/auto-load/auto-load.spec.ts: -------------------------------------------------------------------------------- 1 | import { loadHTMLFile, setupBuildTest, sleep } from '../../utils'; 2 | import path from 'path'; 3 | 4 | beforeAll(() => { 5 | setupBuildTest(__dirname); 6 | }); 7 | 8 | describe('runtime', () => { 9 | it('html', async () => { 10 | const { dom, logFn, errorFn } = await loadHTMLFile( 11 | path.resolve(__dirname, './dist/index.html') 12 | ); 13 | 14 | expect(dom.window.document.body.innerHTML).toMatchSnapshot(); 15 | 16 | await sleep(500); 17 | 18 | expect(dom.window.document.body.innerHTML).toMatchSnapshot(); 19 | 20 | expect(logFn.mock.calls.length).toBe(3); 21 | expect(logFn.mock.calls).toEqual([ 22 | ['Hello First!'], 23 | ['Hello Second!'], 24 | ['Hello Third!'], 25 | ]); 26 | expect(errorFn.mock.calls.length).toBe(0); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/plugins/auto-load/capital/src/index.ts: -------------------------------------------------------------------------------- 1 | import { initMiniStar } from 'mini-star'; 2 | 3 | initMiniStar({ 4 | plugins: [ 5 | { 6 | name: 'first', 7 | url: 'plugins/first/index.js', 8 | }, 9 | { 10 | name: 'third', 11 | url: 'plugins/third/index.js', 12 | }, 13 | ], 14 | pluginUrlBuilder: (pluginName) => `plugins/${pluginName}/index.js`, 15 | }); 16 | -------------------------------------------------------------------------------- /test/plugins/auto-load/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mini-star/test-auto-loading", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "testbuild": "rm -rf ./dist && ../../../node_modules/.bin/webpack --config ../../webpack.test.config.js --stats-error-details && ministar buildPlugin all" 7 | }, 8 | "devDependencies": { 9 | "mini-star": "*" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/plugins/auto-load/plugins/first/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plugins/first", 3 | "main": "src/index.ts", 4 | "private": true 5 | } 6 | -------------------------------------------------------------------------------- /test/plugins/auto-load/plugins/first/src/index.ts: -------------------------------------------------------------------------------- 1 | console.log('Hello First!'); 2 | -------------------------------------------------------------------------------- /test/plugins/auto-load/plugins/first/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./src", 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/plugins/auto-load/plugins/second/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plugins/second", 3 | "main": "src/index.ts", 4 | "private": true 5 | } 6 | -------------------------------------------------------------------------------- /test/plugins/auto-load/plugins/second/src/index.ts: -------------------------------------------------------------------------------- 1 | import '@plugins/first'; 2 | 3 | console.log('Hello Second!'); 4 | -------------------------------------------------------------------------------- /test/plugins/auto-load/plugins/second/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./src", 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/plugins/auto-load/plugins/third/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plugins/third", 3 | "main": "src/index.ts", 4 | "private": true 5 | } 6 | -------------------------------------------------------------------------------- /test/plugins/auto-load/plugins/third/src/index.ts: -------------------------------------------------------------------------------- 1 | import '@plugins/second'; 2 | 3 | console.log('Hello Third!'); 4 | -------------------------------------------------------------------------------- /test/plugins/auto-load/plugins/third/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./src", 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/plugins/simple/__snapshots__/simple.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`plugin Should build simple plugin 1`] = ` 4 | "definePlugin('@plugins/demo', function () { 'use strict'; 5 | 6 | console.log(\\"Hello Demo!\\"); 7 | 8 | }); 9 | //# sourceMappingURL=index.js.map 10 | " 11 | `; 12 | 13 | exports[`runtime html 1`] = `""`; 14 | 15 | exports[`runtime html 2`] = `""`; 16 | -------------------------------------------------------------------------------- /test/plugins/simple/capital/src/index.ts: -------------------------------------------------------------------------------- 1 | import { initMiniStar } from 'mini-star'; 2 | 3 | console.log('Hello World'); 4 | 5 | initMiniStar({ 6 | plugins: [ 7 | { 8 | name: 'demo', 9 | url: 'plugins/demo/index.js', 10 | }, 11 | ], 12 | pluginUrlBuilder: (pluginName) => `plugins/${pluginName}/index.js`, 13 | }); 14 | -------------------------------------------------------------------------------- /test/plugins/simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mini-star/test-simple", 3 | "version": "1.0.0", 4 | "private": true, 5 | "scripts": { 6 | "testbuild": "rm -rf ./dist && ../../../node_modules/.bin/webpack --config ../../webpack.test.config.js && ministar buildPlugin all" 7 | }, 8 | "devDependencies": { 9 | "mini-star": "*" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test/plugins/simple/plugins/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@plugins/demo", 3 | "main": "src/index.ts", 4 | "private": true 5 | } 6 | -------------------------------------------------------------------------------- /test/plugins/simple/plugins/demo/src/index.ts: -------------------------------------------------------------------------------- 1 | console.log('Hello Demo!'); 2 | -------------------------------------------------------------------------------- /test/plugins/simple/plugins/demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "./src", 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/plugins/simple/simple.spec.ts: -------------------------------------------------------------------------------- 1 | import { loadHTMLFile, readFiles, setupBuildTest, sleep } from '../../utils'; 2 | import path from 'path'; 3 | 4 | const output = path.join(__dirname, 'dist'); 5 | 6 | let files: Record = {}; 7 | 8 | beforeAll(() => { 9 | setupBuildTest(__dirname); 10 | 11 | files = readFiles(output); 12 | }); 13 | 14 | describe('plugin', () => { 15 | it('Should build simple plugin', () => { 16 | expect(Object.keys(files)).toEqual([ 17 | '/bundle.js', 18 | '/bundle.js.map', 19 | '/index.html', 20 | '/plugins/demo/index.js', 21 | '/plugins/demo/index.js.map', 22 | ]); 23 | expect(files['/plugins/demo/index.js']).toMatchSnapshot(); 24 | }); 25 | }); 26 | 27 | describe('runtime', () => { 28 | it('html', async () => { 29 | const { dom, logFn, errorFn } = await loadHTMLFile( 30 | path.resolve(__dirname, './dist/index.html') 31 | ); 32 | 33 | expect(dom.window.document.body.innerHTML).toMatchSnapshot(); 34 | 35 | await sleep(500); 36 | 37 | expect(dom.window.document.body.innerHTML).toMatchSnapshot(); 38 | 39 | expect(logFn.mock.calls.length).toBe(2); 40 | expect(logFn.mock.calls).toEqual([['Hello World'], ['Hello Demo!']]); 41 | expect(errorFn.mock.calls.length).toBe(0); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/utils.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import { glob } from 'glob'; 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | import { JSDOM, VirtualConsole } from 'jsdom'; 6 | 7 | const UTF8_FRIENDLY_EXTS = [ 8 | 'css', 9 | 'html', 10 | 'js', 11 | 'map', 12 | 'jsx', 13 | 'ts', 14 | 'tsx', 15 | 'svelte', 16 | 'svg', 17 | 'vue', 18 | 'json', 19 | ]; // only read non-binary files (add more exts here as needed) 20 | 21 | /** 22 | * setup for /tests/plugins/* 23 | */ 24 | export function setupBuildTest(cwd: string) { 25 | return execSync('yarn testbuild', { cwd }); 26 | } 27 | 28 | /** 29 | * Read a directory of files 30 | */ 31 | export function readFiles(directory: string) { 32 | if (typeof directory !== 'string') { 33 | throw new Error(`must specify directory`); 34 | } 35 | 36 | const contents: Record = {}; 37 | const allFiles = glob.sync(`**/*.{${UTF8_FRIENDLY_EXTS.join(',')}}`, { 38 | cwd: directory, 39 | nodir: true, 40 | }); 41 | 42 | allFiles.forEach((filepath) => { 43 | const relativePath = filepath.replace(/^\/?/, '/'); 44 | contents[relativePath] = fs.readFileSync( 45 | path.join(directory, filepath), 46 | 'utf8' 47 | ); 48 | }); 49 | 50 | return contents; 51 | } 52 | 53 | export async function loadHTMLFile(filepath: string) { 54 | const virtualConsole = new VirtualConsole(); 55 | const logFn = jest.fn(); 56 | const debugFn = jest.fn(); 57 | const errorFn = jest.fn(); 58 | const warnFn = jest.fn(); 59 | 60 | virtualConsole.on('log', logFn); 61 | virtualConsole.on('debug', debugFn); 62 | virtualConsole.on('error', errorFn); 63 | virtualConsole.on('warn', warnFn); 64 | 65 | const dom = await JSDOM.fromFile(filepath, { 66 | resources: 'usable', 67 | runScripts: 'dangerously', 68 | virtualConsole, 69 | }); 70 | 71 | return { dom, logFn, debugFn, errorFn, warnFn }; 72 | } 73 | 74 | /** 75 | * JavaScript 中的 sleep 函数 76 | * 参考 https://github.com/sqren/await-sleep/blob/master/index.js 77 | * @param milliseconds 阻塞毫秒 78 | */ 79 | export function sleep(milliseconds: number): Promise { 80 | return new Promise((resolve) => { 81 | setTimeout(resolve, milliseconds); 82 | }); 83 | } 84 | -------------------------------------------------------------------------------- /test/webpack.test.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const path = require('path'); 3 | const HtmlwebpackPlugin = require('html-webpack-plugin'); 4 | 5 | module.exports = { 6 | mode: 'production', 7 | entry: './capital/src/index.ts', 8 | devtool: 'cheap-module-source-map', // for debug 9 | output: { 10 | filename: 'bundle.js', 11 | path: path.resolve(process.cwd(), 'dist'), 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.css$/, 17 | use: [ 18 | // For css 19 | { loader: 'style-loader' }, 20 | { loader: 'css-loader' }, 21 | ], 22 | exclude: /node_modules/, 23 | }, 24 | { 25 | test: /\.tsx?$/, 26 | exclude: /node_modules/, 27 | loader: 'esbuild-loader', 28 | options: { 29 | loader: 'tsx', // Or 'ts' if you don't need tsx 30 | target: 'es2015', 31 | }, 32 | }, 33 | ], 34 | }, 35 | resolve: { 36 | extensions: ['.js', '.tsx', '.ts'], 37 | }, 38 | plugins: [ 39 | new HtmlwebpackPlugin({ 40 | title: 'Plugin Template', 41 | inject: true, 42 | hash: true, 43 | }), 44 | ], 45 | }; 46 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | static/speech 2 | 3 | # Dependencies 4 | /node_modules 5 | 6 | # Production 7 | /build 8 | 9 | # Generated files 10 | .docusaurus 11 | .cache-loader 12 | 13 | # Misc 14 | .DS_Store 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | .env.production.local 19 | 20 | npm-debug.log* 21 | yarn-debug.log* 22 | yarn-error.log* 23 | -------------------------------------------------------------------------------- /website/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /website/.yarnrc: -------------------------------------------------------------------------------- 1 | registry "https://registry.npmjs.org/" 2 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ## Installation 6 | 7 | ```console 8 | yarn install 9 | ``` 10 | 11 | ## Local Development 12 | 13 | ```console 14 | yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ## Build 20 | 21 | ```console 22 | yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ## Deployment 28 | 29 | ```console 30 | GIT_USER= USE_SSH=true yarn deploy 31 | ``` 32 | 33 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 34 | -------------------------------------------------------------------------------- /website/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /website/blog/2019-05-28-hola.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: hola 3 | title: Hola 4 | author: Gao Wei 5 | author_title: Docusaurus Core Team 6 | author_url: https://github.com/wgao19 7 | author_image_url: https://avatars1.githubusercontent.com/u/2055384?v=4 8 | tags: [hola, docusaurus] 9 | --- 10 | 11 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet 12 | -------------------------------------------------------------------------------- /website/blog/2019-05-29-hello-world.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: hello-world 3 | title: Hello 4 | author: Endilie Yacop Sucipto 5 | author_title: Maintainer of Docusaurus 6 | author_url: https://github.com/endiliey 7 | author_image_url: https://avatars1.githubusercontent.com/u/17883920?s=460&v=4 8 | tags: [hello, docusaurus] 9 | --- 10 | 11 | Welcome to this blog. This blog is created with [**Docusaurus 2 alpha**](https://docusaurus.io/). 12 | 13 | 14 | 15 | This is a test post. 16 | 17 | A whole bunch of other information. 18 | -------------------------------------------------------------------------------- /website/blog/2019-05-30-welcome.md: -------------------------------------------------------------------------------- 1 | --- 2 | slug: welcome 3 | title: Welcome 4 | author: Yangshun Tay 5 | author_title: Front End Engineer @ Facebook 6 | author_url: https://github.com/yangshun 7 | author_image_url: https://avatars0.githubusercontent.com/u/1315101?s=400&v=4 8 | tags: [facebook, hello, docusaurus] 9 | --- 10 | 11 | Blog features are powered by the blog plugin. Simply add files to the `blog` directory. It supports tags as well! 12 | 13 | Delete the whole directory if you don't want the blog features. As simple as that! 14 | -------------------------------------------------------------------------------- /website/docs/tutorial/example.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Who needs MiniStar 6 | 7 | A large-scale project needs to decouple its own business functions, and thinks that through plug-ins, a homogeneous technology stack is used to reassemble into a new function project to realize the front-end architecture form of the micro-kernel architecture. 8 | 9 | `MiniStar` is designed to help front-end code plug-in technology to solve a project is not purely functional mix, publish overly complex problems. 10 | 11 | ## Usage scenarios 12 | 13 | The following are some application scenarios that may require `MiniStar` to transform the project: 14 | 15 | - If you want to realize the application of the front-end plug-in center, the realization of the front-end code is completely assembled and assembled by the user 16 | - An application that combines special services and basic services. For example, `TRPG Engine` is an instant messaging application composed of trpg modules, voice modules, and basic chat modules. If you want to provide pure chat services, then the original hybrid architecture It is impossible to achieve-either all or nothing. 17 | - A `toB` application, a `SaaS` platform. Customers must have independent two-opening requirements, and do not want to affect the original functions. Then this part of the code can be provided in the form of a plug-in. 18 | - If a project is too large, it is difficult to compile and release. You can use MiniStar to split the project into sub-projects for independent development and independent deployment. 19 | -------------------------------------------------------------------------------- /website/docs/tutorial/guide/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Guide", 3 | "position": 5 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/tutorial/guide/buildFile.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | --- 4 | 5 | # Build File 6 | 7 | ## ts 8 | 9 | Default support 10 | 11 | ## css 12 | 13 | Default support 14 | 15 | ## less 16 | 17 | Only need to install `less` in your compilation environment, `mini-star` will automatically handle different compilation types 18 | 19 | ``` 20 | npm install --save-dev less 21 | ``` 22 | 23 | ## vue support 24 | 25 | Please check demo: 26 | - [vue3](https://github.com/moonrailgun/mini-star/tree/master/example/vue3) 27 | -------------------------------------------------------------------------------- /website/docs/tutorial/guide/loadLater.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Load Later 6 | 7 | Under normal circumstances, we only need to execute `initMiniStar` at the beginning, but sometimes we need to dynamically add plugins later. 8 | 9 | Consider this scenario: the user opens the plug-in center, selects a plug-in, and clicks to install it. 10 | 11 | If the user needs to refresh the page to take effect, it is a very bad user experience. The best experience is that the user clicks the button and the plug-in can be applied immediately. 12 | 13 | The following interface can dynamically add plug-ins in the future: 14 | ```javascript 15 | import { initMiniStar } from 'mini-star'; 16 | loadSinglePlugin({ name: 'test', url: '/plugins/test/index.js' }); 17 | // Or 18 | loadPluginList([{ name: 'test', url: '/plugins/test/index.js' }]); 19 | ``` 20 | -------------------------------------------------------------------------------- /website/docs/tutorial/guide/ministarrc.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # .ministarrc.json 6 | `MiniStar` has its own configuration file, create the configuration file of `MiniStar` in the root directory (the directory where you run `MiniStar`) 7 | 8 | The spouse file name allows the following examples: 9 | - `.ministarrc` 10 | - `.ministarrc.json` 11 | - `.ministarrc.yaml` 12 | - `.ministarrc.yml` 13 | - `.ministarrc.js` 14 | - `ministar.config.js`: Export a `CommonJS` object, just like `webpack` 15 | 16 | ## Configuration 17 | 18 | ### scope 19 | 20 | Default: `"plugins"` 21 | 22 | The scope named when the plugin `ministar createPlugin` was created, like `@[scope]/[name]` 23 | 24 | ### pluginRoot 25 | 26 | Default: `process.cwd()` 27 | 28 | The path of the folder where the plug-in directory is located. The structure of creating and reading plug-ins is `[pluginRoot]/plugins/[name]` 29 | 30 | ### outDir 31 | 32 | Default: `path.resolve(process.cwd(), './dist')` 33 | 34 | The output directory of the ministar bundler 35 | 36 | ### externalDeps 37 | 38 | Default: `[]` 39 | 40 | External dependencies, the values appearing in this array will not be packaged by the packaging tool. 41 | 42 | ### sourceMap 43 | 44 | Default: `true` 45 | 46 | Whether to generate sourceMap. Default is `true` 47 | 48 | ### author 49 | 50 | Default: `undefined` 51 | 52 | Used to create plugins 53 | 54 | ### license 55 | 56 | Default: `undefined` 57 | 58 | Used to create plugins 59 | 60 | ### rollupPlugins 61 | 62 | Default: `[]` 63 | 64 | An array of `rollup` plugins for adding custom `rollup` plugins 65 | 66 | ### buildRollupPlugins 67 | 68 | Default: `undefined` 69 | 70 | A Function of `rollup` plugin, like `rollupPlugins` but have full control. 71 | 72 | Should return a list for rollup plugins. 73 | 74 | For example: 75 | ```js 76 | const vuePlugin = require('rollup-plugin-vue'); 77 | 78 | module.exports = { 79 | buildRollupPlugins: (plugins) => [ 80 | vuePlugin({ 81 | css: true, 82 | compileTemplate: true, 83 | }), 84 | ...plugins, 85 | ], 86 | }; 87 | ``` 88 | -------------------------------------------------------------------------------- /website/docs/tutorial/guide/pluginDependencies.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Plugin dependency 6 | 7 | Plugins are allowed to depend on each other, and `mini-star` will automatically handle the topological ordering of plugin dependencies. 8 | 9 | ## How to use 10 | 11 | **./plugins/alert/src/index.ts**: 12 | ```typescript 13 | import Swal from 'sweetalert2'; 14 | 15 | export function alert(text: string) { 16 | Swal.fire(text); 17 | } 18 | 19 | ``` 20 | 21 | **./plugins/test/src/index.ts**: 22 | ```typescript 23 | import { alert as myAlert } from '@plugins/alert'; 24 | 25 | console.log('myAlert', myAlert); 26 | myAlert('Test Alert'); 27 | ``` 28 | 29 | ## Custom plugin address auto-completion logic 30 | 31 | Custom configuration at runtime using `pluginUrlBuilder` 32 | 33 | ```typescript 34 | import { initMiniStar } from 'mini-star'; 35 | 36 | initMiniStar({ 37 | pluginUrlBuilder: (pluginName: string) => '/path/to/pluginDir' // for example: (pluginName) => `http://localhost:3000/plugins/${pluginName}/index.js` 38 | }) 39 | ``` 40 | 41 | ## Example 42 | 43 | - [full example](https://github.com/moonrailgun/mini-star/tree/master/example/full) 44 | -------------------------------------------------------------------------------- /website/docs/tutorial/guide/reduceEntry.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | --- 4 | 5 | # Reduce plugin entry code 6 | 7 | When using `MiniStar`, in order to ensure the timing of the code, it may be necessary to load the plug-in code before loading the main project code. 8 | 9 | At this time, the size of the plug-in code itself is very important. 10 | 11 | We should try our best to ensure the size of the plug-in code entry file. 12 | 13 | ### Dynamic loading 14 | 15 | Use dynamic loading as much as possible to split internal logic 16 | 17 | 18 | ```javascript 19 | import('/path/to/other/logic').then(() => { 20 | // ... 21 | }) 22 | ``` 23 | 24 | ### Asynchronous component 25 | 26 | If you are using a UI framework such as `React`, you can use a dynamically loaded asynchronous component to instead of the original direct introduction. 27 | - [@loadable/component](https://www.npmjs.com/package/@loadable/component) 28 | -------------------------------------------------------------------------------- /website/docs/tutorial/guide/shareDependencies.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Shared dependency 6 | 7 | The plugin itself can have its own independent dependencies, but in the face of the same dependency as the base project, do I need to re-package the plug-in? 8 | 9 | Of course not! `mini-star` chooses to share dependencies. Although there may be hidden dangers of forced upgrade of plug-in dependencies due to the dependency upgrade of the base project, compared with the benefits of code size reduction brought by its sharing, mini-star thinks it is worthwhile 10 | 11 | ## Shared node_module dependency 12 | 13 | ```javascript 14 | import { initMiniStar, regDependency, regSharedModule } from 'mini-star'; 15 | 16 | regDependency('react', () => import('react')); 17 | ``` 18 | 19 | ## Shared base project module 20 | 21 | ```javascript 22 | regSharedModule( 23 | '@capital/common', 24 | () => import('./common') 25 | ); 26 | ``` 27 | 28 | Note: The dependency of the base project needs to start with `@capital/`, `capital` is the name of the base project by `mini-star`. 29 | 30 | You should also obey this rule when using it 31 | 32 | ```javascript 33 | // in plugin 34 | import * as common from '@capital/common' 35 | ``` 36 | -------------------------------------------------------------------------------- /website/docs/tutorial/guide/webpack.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | --- 4 | 5 | # Webpack 6 | 7 | ## Development Environment 8 | 9 | Using `contentBase` in the `webpack` configuration can help `webpack-dev-server` proxy the plugin directory, such as: 10 | 11 | ```javascript 12 | { 13 | // ... 14 | devServer: { 15 | contentBase: './dist', 16 | }, 17 | // ... 18 | } 19 | ``` 20 | -------------------------------------------------------------------------------- /website/docs/tutorial/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Intro 6 | 7 | `mini-star` is a front-end library for the realization of the project's micro-kernel(pluginize), which aims to help you build(or migrate to) a production-usable micro-kernel (plug-in) architecture system more simply and painlessly. 8 | 9 | 10 | 11 | ## What is Microkernel Architecture 12 | 13 | It is composed of a group of software programs that minimize the number as much as possible, and they are responsible for providing and implementing various mechanisms and functions required by an operating system. These most basic mechanisms include low-level address space management, thread management, and inter-process communication. 14 | 15 | ![Microkernel Architecture](/img/docs/intro.png) 16 | 17 | ## Design concept 18 | 19 | The realization of the system is distinguished from the basic operating rules of the system. The way it is implemented is to modularize the core functions, divide them into several independent processes, and run them separately. These processes are called services. All service processes are running in different address spaces. 20 | 21 | Making services independent of each other can reduce the degree of coupling between systems, facilitate implementation and debugging, and improve portability. It can prevent a single component from failing and causing the entire system to crash. The kernel only needs to restart this component, which will not affect the functions of other servers and increase the stability of the system. At the same time, business functions can be replaced or added to certain service processes as needed to make the functions more flexible. 22 | 23 | In terms of the amount of code, generally speaking, because of simplified functions, the core system uses less code than the integrated system. Less code means fewer hidden bugs. 24 | 25 | ## ministar's design concept 26 | 27 | - Simple 28 | 29 | Since both the base project and the plug-in project can achieve the technology stack irrelevant, `ministar` is just a library similar to the jQuery plug-in system for users. You need to load the plug-in and shared dependent components through `ministar/runtime`, and then use `ministar/ Bundler` can build the plug-in project to realize the plug-in transformation of the original system. 30 | 31 | - Decoupling/Technology Stack Independent 32 | 33 | The core goal of the microkernel is the same as that of the micro front end. It is to disassemble the boulder application into a number of loosely coupled micro applications that can be autonomous. Many designs of ministar adhere to this principle, except for the shared public dependencies and the base project. Ability, the plug-in project has its own context, dependency management, and mutual communication mechanism, so as to ensure that the plug-in has the ability to develop independently. And to ensure the ability to share types with other dependencies. 34 | 35 | ## Feature 36 | 37 | - Out of the box, it can also be customized. 38 | - The technology stack has nothing to do with the application of any technology stack can be `used/accessed`, whether it is React/Vue/Angular/Svelte/JQuery or other frameworks. 39 | - Shared dependencies, the same dependencies only need to be loaded once, reducing unnecessary volume and packaging time 40 | - Make dependency calls between plugins like calling native components 41 | - Packing based on `Rollup`, fast! 42 | - Born for the modern front end. In the past, we exposed methods through windows, now all our code needs to be compiled into modules, and exposure is also through modules 43 | - Topology relies on sorting to prevent timing problems 44 | 45 | ## Document 46 | 47 | - [Official](https://ministar.moonrailgun.com/) 48 | - [Github](https://github.com/moonrailgun/mini-star) 49 | - [微内核架构在大型前端系统中的应用(微前端)](https://segmentfault.com/a/1190000016862735) 50 | -------------------------------------------------------------------------------- /website/docs/tutorial/quickstart/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Quick Start", 3 | "position": 4 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/tutorial/quickstart/createPlugin.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # Create Plugin 6 | 7 | We can quickly create a new plugin via `cli`. 8 | 9 | ```bash 10 | npx mini-star createPlugin 11 | ``` 12 | 13 | `mini-star` will ask some questions through an interactive terminal program to finally create a plugin. 14 | 15 | Plugins will be automatically placed in the `plugins/` directory of the root directory. 16 | 17 | Now, let's try to create a plugin called `test`: 18 | 19 | ![](/img/docs/createPlugin.jpg) 20 | 21 | At this time, the `/plugins` directory should be as follows: 22 | ``` 23 | ./plugins 24 | └── test 25 | ├── package.json 26 | ├── src 27 | │   └── index.ts 28 | └── tsconfig.json 29 | ``` 30 | 31 | One of the simplest plugins is complete, start your plugin journey with `index.ts` as the entrance 32 | 33 | ## Compile the plugin 34 | 35 | In order for the plugin to be loaded correctly, don't forget to use ministar to compile the plug-in code after each modification. By default, it will be packaged and output to the `dist/plugins` directory of the current directory. 36 | 37 | You can also modify the output by modifying [outDir](../guide/ministarrc#outdir) in the configuration file. 38 | 39 | ```bash 40 | npx mini-star buildPlugin test 41 | 42 | # or 43 | 44 | npx mini-star buildPlugin all 45 | ``` 46 | -------------------------------------------------------------------------------- /website/docs/tutorial/quickstart/install.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Install 6 | 7 | The installation of `mini-star` is very simple, it only takes one step to complete 8 | 9 | ```bash npm2yarn 10 | npm install mini-star 11 | ``` 12 | 13 | mini-star consists of three parts: `runtime`, `bundler` and `cli` 14 | 15 | Let's check if the `cli` of `mini-star` is working properly. 16 | 17 | ```bash 18 | npx mini-star --help 19 | ``` 20 | 21 | Let us quickly go to [Next Step](./createPlugin). 22 | -------------------------------------------------------------------------------- /website/docs/tutorial/quickstart/usePlugin.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Use Plugin 6 | 7 | The base project can be any project, just like a normal project. 8 | 9 | Just need to initialize the project through `mini-star` in the right place 10 | 11 | For example: 12 | ```javascript 13 | import { initMiniStar } from 'mini-star'; 14 | 15 | console.log('App start'); 16 | 17 | initMiniStar({ 18 | plugins: [ 19 | { 20 | name: 'test', 21 | url: '/plugins/test/index.js', // NOTICE: Please make sure that the address can be accessed 22 | }, 23 | ], 24 | }).then(() => { 25 | console.log('Plugin Load Success'); 26 | }); 27 | ``` 28 | 29 | The list in `plugins` can come from anywhere. This tutorial provides a fixed array for convenience. In the actual production environment, it can come from an `App Store` or a user's manual selection. 30 | 31 | After packaging the plug-in and base project, the output structure tree should be like this: 32 | 33 | ``` 34 | . 35 | ├── bundle.js 36 | ├── bundle.js.map 37 | ├── index.html 38 | └── plugins 39 | └── test 40 | ├── index.js 41 | └── index.js.map 42 | ``` 43 | 44 | Start a static file service (such as `http-server`) and take a look. 45 | 46 | Console Output: 47 | ![](/img/docs/usePlugin.jpg) 48 | 49 | - The first line comes from the first `console.log` of the base project 50 | - The second line of `Hello World` comes from the content of the `test` plugin 51 | - The third line comes from the callback when the dock project plug-in is loaded 52 | 53 | As you can see, we simply added the plug-in function to an existing project. `mini-star` is so simple! 54 | 55 | ## Special Mark 56 | - `@capital/*` Base project exported dependencies 57 | - `@plugins/*` Plugin project exported dependencies 58 | -------------------------------------------------------------------------------- /website/docs/tutorial/speech/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Speech", 3 | "position": 6 4 | } 5 | -------------------------------------------------------------------------------- /website/docs/tutorial/speech/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # Index 6 | 7 | - Intro(chinese) 8 | -------------------------------------------------------------------------------- /website/docs/tutorial/why.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # Why is MiniStar 6 | 7 | `MiniStar` is a simple and open source front-end progressive plug-in framework. 8 | 9 | It provides a complete set of `cli/runtime/bundler` development links. If you want to customize it, it's very simple. And MiniStar's design is gradual, even existing projects can be plug-ined little by little without having to complete them all at once. 10 | 11 | ## Difference from Mini Program 12 | 13 | `MiniStar` and `Mini Program` are more similar, because they both develop and deploy some functions as independent sub-projects. The difference is that the communication between `MiniStar` and the main program is closer, and the content to be reused is more. And there is no complicated runtime. 14 | 15 | `Mini Program` is more suitable for occasions with higher security scenarios, while `MiniStar` plugins trust the plugin code more. 16 | 17 | In addition, small programs are more suitable for `app`, while `MiniStar` is born for `web` 18 | 19 | ## Difference from qiankun 20 | 21 | `MiniStar` is a micro-kernel framework, and `qiankun` is a micro front-end framework 22 | 23 | `MiniStar` pays more attention to the reuse of dependencies and the provision of main application capabilities, and emphasizes the isomorphism of the technology stack 24 | 25 | `qiankun` is to solve the scenario where multiple heterogeneous sub-applications are running in the same main application. Pay more attention to the isolation of dependencies and code 26 | 27 | They are two solutions, and they solve different problems. 28 | 29 | If you are looking for a micro front-end solution, you can look at [`qiankun`](https://qiankun.umijs.org/) 30 | 31 | ## Difference from requirejs 32 | 33 | The implementation of `requirejs` is very similar to that of `MiniStar`. 34 | 35 | But the smallest combination unit of `requirejs` is file 36 | 37 | The smallest unit of `MiniStar` is a module. 38 | 39 | `MiniStar` is more in line with modern front-end module ideas, and will not affect existing code habits. 40 | -------------------------------------------------------------------------------- /website/docusaurus.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('@docusaurus/types').DocusaurusConfig} */ 2 | module.exports = { 3 | title: 'MiniStar', 4 | tagline: 'Pluginize your project', 5 | url: 'https://ministar.moonrailgun.com/', 6 | baseUrl: '/', 7 | onBrokenLinks: 'throw', 8 | onBrokenMarkdownLinks: 'warn', 9 | favicon: 'img/favicon.ico', 10 | organizationName: 'moonrailgun', // Usually your GitHub org/user name. 11 | projectName: 'mini-star', // Usually your repo name. 12 | i18n: { 13 | defaultLocale: 'en', 14 | locales: ['en', 'zh-Hans'], 15 | localeConfigs: { 16 | en: { 17 | label: 'English', 18 | }, 19 | 'zh-Hans': { 20 | label: '简体中文', 21 | }, 22 | }, 23 | }, 24 | themeConfig: { 25 | navbar: { 26 | title: 'MiniStar', 27 | logo: { 28 | alt: 'MiniStar Logo', 29 | src: 'img/logo.svg', 30 | }, 31 | items: [ 32 | { 33 | type: 'doc', 34 | docId: 'tutorial/intro', 35 | position: 'left', 36 | label: 'Tutorial', 37 | }, 38 | // { to: '/blog', label: 'Blog', position: 'left' }, 39 | { 40 | href: 'https://github.com/moonrailgun/mini-star', 41 | label: 'GitHub', 42 | position: 'right', 43 | }, 44 | { 45 | type: 'localeDropdown', 46 | position: 'right', 47 | }, 48 | ], 49 | }, 50 | footer: { 51 | style: 'dark', 52 | links: [ 53 | { 54 | title: 'Docs', 55 | items: [ 56 | { 57 | label: 'Tutorial', 58 | to: '/docs/tutorial/intro', 59 | }, 60 | ], 61 | }, 62 | // { 63 | // title: 'Community', 64 | // items: [ 65 | // { 66 | // label: 'Stack Overflow', 67 | // href: 'https://stackoverflow.com/questions/tagged/docusaurus', 68 | // }, 69 | // { 70 | // label: 'Discord', 71 | // href: 'https://discordapp.com/invite/docusaurus', 72 | // }, 73 | // { 74 | // label: 'Twitter', 75 | // href: 'https://twitter.com/docusaurus', 76 | // }, 77 | // ], 78 | // }, 79 | { 80 | title: 'More', 81 | items: [ 82 | // { 83 | // label: 'Blog', 84 | // to: '/blog', 85 | // }, 86 | { 87 | label: 'GitHub', 88 | href: 'https://github.com/moonrailgun/mini-star', 89 | }, 90 | ], 91 | }, 92 | ], 93 | copyright: `Copyright © ${new Date().getFullYear()} moonrailgun. Built with Docusaurus.`, 94 | }, 95 | }, 96 | presets: [ 97 | [ 98 | '@docusaurus/preset-classic', 99 | { 100 | docs: { 101 | sidebarPath: require.resolve('./sidebars.js'), 102 | // Please change this to your repo. 103 | // editUrl: 104 | // 'https://github.com/facebook/docusaurus/edit/master/website/', 105 | remarkPlugins: [ 106 | [require('@docusaurus/remark-plugin-npm2yarn'), { sync: true }], 107 | ], 108 | }, 109 | blog: false, 110 | // blog: { 111 | // showReadingTime: true, 112 | // // Please change this to your repo. 113 | // // editUrl: 114 | // // 'https://github.com/facebook/docusaurus/edit/master/website/blog/', 115 | // remarkPlugins: [ 116 | // [require('@docusaurus/remark-plugin-npm2yarn'), { sync: true }], 117 | // ], 118 | // }, 119 | pages: { 120 | remarkPlugins: [ 121 | [require('@docusaurus/remark-plugin-npm2yarn'), { sync: true }], 122 | ], 123 | }, 124 | theme: { 125 | customCss: require.resolve('./src/css/custom.css'), 126 | }, 127 | }, 128 | ], 129 | ], 130 | scripts: [ 131 | { 132 | src: 'https://umami.moonrailgun.com/script.js', 133 | async: true, 134 | defer: true, 135 | 'data-website-id': 'a14bc2b9-45b2-40ce-a94f-35d72778984c', 136 | }, 137 | ], 138 | }; 139 | -------------------------------------------------------------------------------- /website/i18n/zh-Hans/code.json: -------------------------------------------------------------------------------- 1 | { 2 | "theme.NotFound.title": { 3 | "message": "找不到页面", 4 | "description": "The title of the 404 page" 5 | }, 6 | "theme.NotFound.p1": { 7 | "message": "我们找不到您要找的页面。", 8 | "description": "The first paragraph of the 404 page" 9 | }, 10 | "theme.NotFound.p2": { 11 | "message": "请联系原始链接来源网站的所有者,并告知他们链接已损坏。", 12 | "description": "The 2nd paragraph of the 404 page" 13 | }, 14 | "theme.AnnouncementBar.closeButtonAriaLabel": { 15 | "message": "关闭", 16 | "description": "The ARIA label for close button of announcement bar" 17 | }, 18 | "theme.blog.paginator.navAriaLabel": { 19 | "message": "博文列表分页导航", 20 | "description": "The ARIA label for the blog pagination" 21 | }, 22 | "theme.blog.paginator.newerEntries": { 23 | "message": "较新的博文", 24 | "description": "The label used to navigate to the newer blog posts page (previous page)" 25 | }, 26 | "theme.blog.paginator.olderEntries": { 27 | "message": "较旧的博文", 28 | "description": "The label used to navigate to the older blog posts page (next page)" 29 | }, 30 | "theme.blog.post.readingTime.plurals": { 31 | "message": "{readingTime} 分钟阅读", 32 | "description": "Pluralized label for \"{readingTime} min read\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" 33 | }, 34 | "theme.tags.tagsListLabel": { 35 | "message": "标签:", 36 | "description": "The label alongside a tag list" 37 | }, 38 | "theme.blog.post.readMore": { 39 | "message": "阅读更多", 40 | "description": "The label used in blog post item excerpts to link to full blog posts" 41 | }, 42 | "theme.blog.post.paginator.navAriaLabel": { 43 | "message": "博文分页导航", 44 | "description": "The ARIA label for the blog posts pagination" 45 | }, 46 | "theme.blog.post.paginator.newerPost": { 47 | "message": "较新一篇", 48 | "description": "The blog post button label to navigate to the newer/previous post" 49 | }, 50 | "theme.blog.post.paginator.olderPost": { 51 | "message": "较旧一篇", 52 | "description": "The blog post button label to navigate to the older/next post" 53 | }, 54 | "theme.tags.tagsPageTitle": { 55 | "message": "标签", 56 | "description": "The title of the tag list page" 57 | }, 58 | "theme.blog.post.plurals": { 59 | "message": "{count} 篇博文", 60 | "description": "Pluralized label for \"{count} posts\". Use as much plural forms (separated by \"|\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)" 61 | }, 62 | "theme.blog.tagTitle": { 63 | "message": "{nPosts} 含有标签「{tagName}」", 64 | "description": "The title of the page for a blog tag" 65 | }, 66 | "theme.tags.tagsPageLink": { 67 | "message": "查看所有标签", 68 | "description": "The label of the link targeting the tag list page" 69 | }, 70 | "theme.CodeBlock.copyButtonAriaLabel": { 71 | "message": "复制代码到剪贴板", 72 | "description": "The ARIA label for copy code blocks button" 73 | }, 74 | "theme.CodeBlock.copied": { 75 | "message": "复制成功", 76 | "description": "The copied button label on code blocks" 77 | }, 78 | "theme.CodeBlock.copy": { 79 | "message": "复制", 80 | "description": "The copy button label on code blocks" 81 | }, 82 | "theme.docs.sidebar.expandButtonTitle": { 83 | "message": "展开侧边栏", 84 | "description": "The ARIA label and title attribute for expand button of doc sidebar" 85 | }, 86 | "theme.docs.sidebar.expandButtonAriaLabel": { 87 | "message": "展开侧边栏", 88 | "description": "The ARIA label and title attribute for expand button of doc sidebar" 89 | }, 90 | "theme.docs.paginator.navAriaLabel": { 91 | "message": "文档分页导航", 92 | "description": "The ARIA label for the docs pagination" 93 | }, 94 | "theme.docs.paginator.previous": { 95 | "message": "上一页", 96 | "description": "The label used to navigate to the previous doc" 97 | }, 98 | "theme.docs.paginator.next": { 99 | "message": "下一页", 100 | "description": "The label used to navigate to the next doc" 101 | }, 102 | "theme.docs.sidebar.collapseButtonTitle": { 103 | "message": "收起侧边栏", 104 | "description": "The title attribute for collapse button of doc sidebar" 105 | }, 106 | "theme.docs.sidebar.collapseButtonAriaLabel": { 107 | "message": "收起侧边栏", 108 | "description": "The title attribute for collapse button of doc sidebar" 109 | }, 110 | "theme.docs.sidebar.responsiveCloseButtonLabel": { 111 | "message": "关闭菜单", 112 | "description": "The ARIA label for close button of mobile doc sidebar" 113 | }, 114 | "theme.docs.sidebar.responsiveOpenButtonLabel": { 115 | "message": "打开菜单", 116 | "description": "The ARIA label for open button of mobile doc sidebar" 117 | }, 118 | "theme.docs.versions.unreleasedVersionLabel": { 119 | "message": "此为 {siteTitle} {versionLabel} 版尚未发行的文档。", 120 | "description": "The label used to tell the user that he's browsing an unreleased doc version" 121 | }, 122 | "theme.docs.versions.unmaintainedVersionLabel": { 123 | "message": "此为 {siteTitle} {versionLabel} 版的文档,现已不再积极维护。", 124 | "description": "The label used to tell the user that he's browsing an unmaintained doc version" 125 | }, 126 | "theme.docs.versions.latestVersionSuggestionLabel": { 127 | "message": "最新的文档请参阅 {latestVersionLink} ({versionLabel})。", 128 | "description": "The label userd to tell the user that he's browsing an unmaintained doc version" 129 | }, 130 | "theme.docs.versions.latestVersionLinkLabel": { 131 | "message": "最新版本", 132 | "description": "The label used for the latest version suggestion link label" 133 | }, 134 | "theme.common.editThisPage": { 135 | "message": "编辑此页", 136 | "description": "The link label to edit the current page" 137 | }, 138 | "theme.common.headingLinkTitle": { 139 | "message": "标题的直接链接", 140 | "description": "Title for link to heading" 141 | }, 142 | "theme.lastUpdated.atDate": { 143 | "message": "于 {date} ", 144 | "description": "The words used to describe on which date a page has been last updated" 145 | }, 146 | "theme.lastUpdated.byUser": { 147 | "message": "由 {user} ", 148 | "description": "The words used to describe by who the page has been last updated" 149 | }, 150 | "theme.lastUpdated.lastUpdatedAtBy": { 151 | "message": "最后{byUser}{atDate}更新", 152 | "description": "The sentence used to display when a page has been last updated, and by who" 153 | }, 154 | "theme.common.skipToMainContent": { 155 | "message": "跳到主要内容", 156 | "description": "The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation" 157 | }, 158 | "homepage.feature1": { 159 | "message": "使用 {title} 来开始您的项目或将其注入现有项目。她是渐进式的!" 160 | }, 161 | "homepage.feature2": { 162 | "message": "解耦是对工程非常重要的一部分。通过动态代码来帮助您拆分项目,使其可以独立开发和独立部署。" 163 | }, 164 | "homepage.feature3": { 165 | "message": "{title} 是一个框架无关的框架,无论是 react、vue、svelte、jquery 还是 angular。 您可以为此安心: 它为现代 Web 项目而创建。" 166 | }, 167 | "Start with MiniStar": { 168 | "message": "从 Ministar 开始" 169 | }, 170 | "Pluginize your Project": { 171 | "message": "插件化您的项目" 172 | }, 173 | "For Any Framework": { 174 | "message": "框架无关" 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /website/i18n/zh-Hans/docusaurus-plugin-content-docs/current.json: -------------------------------------------------------------------------------- 1 | { 2 | "version.label": { 3 | "message": "Next", 4 | "description": "The label for version current" 5 | }, 6 | "sidebar.tutorial.category.Quick Start": { 7 | "message": "快速上手", 8 | "description": "The label for category Quick Start in sidebar tutorial" 9 | }, 10 | "sidebar.tutorial.category.Guide": { 11 | "message": "引导手册", 12 | "description": "The label for category Guide in sidebar tutorial" 13 | } 14 | } -------------------------------------------------------------------------------- /website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/tutorial/example.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # 谁需要MiniStar 6 | 7 | 一个大型项目需要将自己的业务功能解耦,并且认为通过插件的方式,以同构的技术栈来重新组合成新功能的项目,以实现微内核架构的前端架构形式。 8 | 9 | `MiniStar`的设计目的是帮助前端代码插件化,来解决一个项目功能混杂不纯粹、发布过于复杂的问题。 10 | 11 | ## 使用场景 12 | 13 | 以下是一些可能需要`MiniStar`对项目进行改造的应用场景: 14 | 15 | - 想要实现前端插件中心的应用,前端代码的实现完全由用户来自己拼装组合 16 | - 一个由特殊业务与基础业务组合的应用,比如`TRPG Engine`是由trpg模块,语音模块,与基础聊天模块组合成的即时通讯应用,如果想要提供纯粹的聊天服务,那么原来混合的架构是无法实现的 —— 要么全有,要么全无。 17 | - 一个`toB`端的应用,一个`SaaS`平台。客户一定会有独立的二开需求,而不想影响到原有功能。那么这部分代码则可以由插件的形式来提供。 18 | - 一个项目过于庞大导致编译困难,发布困难,可以通过`MiniStar`将项目拆成一个个子项目,来进行独立开发独立部署。 19 | -------------------------------------------------------------------------------- /website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/tutorial/guide/buildFile.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | --- 4 | 5 | # 构建文件 6 | 7 | ## ts 8 | 9 | 默认支持 10 | 11 | ## css 12 | 13 | 默认支持 14 | 15 | ## less 16 | 17 | 仅需要在你的编译环境中安装`less`即可,`mini-star` 会自动去处理不同的编译类型 18 | 19 | ``` 20 | npm install --save-dev less 21 | ``` 22 | -------------------------------------------------------------------------------- /website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/tutorial/guide/loadLater.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # 稍后加载 6 | 7 | 一般情况下,我们只需要在开始的时候执行`initMiniStar`即可, 但是有时候我们需要在后续动态追加插件。 8 | 9 | 考虑一下这个场景: 用户打开了插件中心,选择了一个插件,然后点击了一下安装。 10 | 11 | 对于如果需要用户去刷新页面才能生效的话,是一种非常不好的用户体验。最好的体验是用户点击按钮,即可立即应用插件。 12 | 13 | 如下接口可以在后续动态追加插件: 14 | ```javascript 15 | import { initMiniStar } from 'mini-star'; 16 | loadSinglePlugin({ name: 'test', url: '/plugins/test/index.js' }); 17 | // Or 18 | loadPluginList([{ name: 'test', url: '/plugins/test/index.js' }]); 19 | ``` 20 | -------------------------------------------------------------------------------- /website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/tutorial/guide/ministarrc.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 4 3 | --- 4 | 5 | # .ministarrc.json 6 | 7 | `MiniStar` 具有自己的配置文件,在根目录(运行`MiniStar`的目录)下创建`MiniStar`的配置文件 8 | 9 | 配偶文件名称允许如下示例: 10 | - `.ministarrc` 11 | - `.ministarrc.json` 12 | - `.ministarrc.yaml` 13 | - `.ministarrc.yml` 14 | - `.ministarrc.js` 15 | - `ministar.config.js`: 导出一个`CommonJS`对象, 就像`webpack`一样 16 | 17 | ## 配置项 18 | 19 | ### scope 20 | 21 | 默认值: `"plugins"` 22 | 23 | 创建插件`ministar createPlugin`时命名的scope, 形如`@[scope]/[name]` 24 | 25 | ### pluginRoot 26 | 27 | 默认值: `process.cwd()` 28 | 29 | 插件目录所在文件夹路径。创建与读取插件的结构为`[pluginRoot]/plugins/[name]` 30 | 31 | ### outDir 32 | 33 | 默认值: `path.resolve(process.cwd(), './dist')` 34 | 35 | 插件打包的输出目录 36 | 37 | ### externalDeps 38 | 39 | 默认值: `[]` 40 | 41 | 外部依赖, 在这个数组中出现的值将不会被打包工具打包进去。 42 | 43 | ### sourceMap 44 | 45 | 默认值: `true` 46 | 47 | 是否生成sourceMap。默认为 `true` 48 | 49 | ### author 50 | 51 | 默认值: `undefined` 52 | 53 | 用于创建插件 54 | 55 | ### license 56 | 57 | 默认值: `undefined` 58 | 59 | 用于创建插件 60 | 61 | ### rollupPlugins 62 | 63 | 默认值: `[]` 64 | 65 | 一个`rollup`插件数组,用于添加自定义的`rollup`插件 66 | 67 | ### buildRollupPlugins 68 | 69 | 默认值: `undefined` 70 | 71 | 一个用于构建`rollup`插件列表的函数,类似于`rollupPlugins`但是对于插件列表拥有完整控制权 72 | 73 | 应当返回一个插件列表数组 74 | 75 | 示例代码: 76 | ```js 77 | const vuePlugin = require('rollup-plugin-vue'); 78 | 79 | module.exports = { 80 | buildRollupPlugins: (plugins) => [ 81 | vuePlugin({ 82 | css: true, 83 | compileTemplate: true, 84 | }), 85 | ...plugins, 86 | ], 87 | }; 88 | ``` 89 | -------------------------------------------------------------------------------- /website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/tutorial/guide/pluginDependencies.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # 插件依赖 6 | 7 | 插件之间允许相互依赖,`mini-star` 会自动处理插件依赖的拓扑排序。 8 | 9 | ## 使用方式 10 | 11 | **./plugins/alert/src/index.ts**: 12 | ```typescript 13 | import Swal from 'sweetalert2'; 14 | 15 | export function alert(text: string) { 16 | Swal.fire(text); 17 | } 18 | 19 | ``` 20 | 21 | **./plugins/test/src/index.ts**: 22 | ```typescript 23 | import { alert as myAlert } from '@plugins/alert'; 24 | 25 | console.log('myAlert', myAlert); 26 | myAlert('Test Alert'); 27 | ``` 28 | 29 | ## 自定义插件的地址自动补全逻辑 30 | 31 | 使用 `pluginUrlBuilder` 在运行时中进行自定义配置 32 | 33 | ```typescript 34 | import { initMiniStar } from 'mini-star'; 35 | 36 | initMiniStar({ 37 | pluginUrlBuilder: (pluginName: string) => '/path/to/pluginDir' // 示例: (pluginName) => `http://localhost:3000/plugins/${pluginName}/index.js` 38 | }) 39 | ``` 40 | 41 | 42 | ## 示例 43 | 44 | - [完整示例](https://github.com/moonrailgun/mini-star/tree/master/example/full) 45 | -------------------------------------------------------------------------------- /website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/tutorial/guide/reduceEntry.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 5 3 | --- 4 | 5 | # 缩减插件入口代码 6 | 7 | 在`MiniStar`使用的场合,为了保证代码的时序性可能需要先加载插件代码以后再去加载主项目代码。 8 | 9 | 在这时候插件代码本身的体积就非常重要。 10 | 11 | 我们应当尽可能保证插件代码入口文件的体积。 12 | 13 | ### 动态加载 14 | 15 | 尽可能使用动态加载以分割内部逻辑 16 | 17 | ```javascript 18 | import('/path/to/other/logic').then(() => { 19 | // ... 20 | }) 21 | ``` 22 | 23 | ### 异步组件 24 | 25 | 如果使用的是`React`这样的UI框架,可以使用动态加载的异步组件来代替原来的直接引入。 26 | - [@loadable/component](https://www.npmjs.com/package/@loadable/component) 27 | -------------------------------------------------------------------------------- /website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/tutorial/guide/shareDependencies.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # 共享依赖 6 | 7 | 插件本身可以有自己独立的依赖,但面对与基座项目具有相同依赖的情况下,还需要重复打包插件么? 8 | 9 | 当然不!`mini-star`选择共享依赖。虽然可能会有因为基座项目依赖升级导致插件依赖被迫升级的隐患,但是相比其共享带来的代码体积减少的收益, `mini-star`认为这是值得的 10 | 11 | ## 共享 node_module 依赖 12 | 13 | ```javascript 14 | import { initMiniStar, regDependency, regSharedModule } from 'mini-star'; 15 | 16 | regDependency('react', () => import('react')); 17 | ``` 18 | 19 | ## 共享基座项目模块 20 | 21 | ```javascript 22 | regSharedModule( 23 | '@capital/common', 24 | () => import('./common') 25 | ); 26 | ``` 27 | 28 | 注意: 基座项目的依赖需要以`@capital/`开头, `capital`是`mini-star`对基座项目的称呼。 29 | 30 | 在使用时也应当准守这个规则 31 | 32 | ```javascript 33 | // in plugin 34 | import * as common from '@capital/common' 35 | ``` 36 | -------------------------------------------------------------------------------- /website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/tutorial/guide/webpack.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 6 3 | --- 4 | 5 | # Webpack 6 | 7 | ## 开发环境 8 | 9 | 在 `webpack` 配置中使用 `contentBase` 可以帮助 `webpack-dev-server` 代理插件目录,如: 10 | 11 | ```javascript 12 | { 13 | // ... 14 | devServer: { 15 | contentBase: './dist', 16 | }, 17 | // ... 18 | } 19 | ``` 20 | -------------------------------------------------------------------------------- /website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/tutorial/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # 介绍 6 | 7 | `mini-star` 是一个为实现项目微内核(插件化)前端库,旨在帮助大家能更简单、无痛的构建(或改造成)一个生产可用微内核(插件化)架构系统。 8 | 9 | ## 什么是微内核 10 | 11 | 由一群尽可能将数量最小化的软件程序组成,他们负责提供、实现一个操作系统所需要的各种机制和功能。这些最基础的机制,包括了底层地址空间管理,线程管理,与进程间通讯。 12 | 13 | ![微内核架构图](/img/docs/intro.png) 14 | 15 | ## 设计理念 16 | 17 | 将系统的实现,与系统的基本操作规则区分开来。它实现的方式是将核心功能模块化,划分成几个独立的进程,各自运行,这些进程被称为服务。所有的服务进程,都运行在不同的地址空间。 18 | 19 | 让服务各自独立,可以减少系统之间的耦合度,易于实现与除错,也可以增进可移植性。它可以避免单一组件失效,而造成整个系统崩溃,内核只需要重启这个组件,不至于影响其他服务器的功能,使系统稳定度增加。同时业务功能可以视需要,抽换或新增某些服务进程,使功能更有弹性。 20 | 21 | 就代码数量来看,一般来说,因为功能简化,核心系统使用的代码比集成式系统更少。更少的代码意味更少的潜藏程序bug。 22 | 23 | ## ministar 的核心设计理念 24 | 25 | - 简单 26 | 27 | 由于基座项目与插件项目都能做到技术栈无关,ministar 对于用户而言只是一个类似 jQuery 插件系统的库,你需要通过 `ministar/runtime` 来加载插件与共享依赖组件,然后用 `ministar/bundler` 来构建插件项目, 即可实现原系统插件化的改造。 28 | 29 | - 解耦/技术栈无关 30 | 31 | 微内核的核心目标与微前端一样,是将巨石应用拆解成若干可以自治的松耦合微应用,而 ministar 的诸多设计均是秉持这一原则,除了需要共享的公共依赖与基座项目提供的能力,插件项目拥有自己的上下文,依赖管理,以及相互的通讯机制,这样来保证插件具有独立开发的能力。并保证了与其它依赖共享类型的能力。 32 | 33 | ## 特性 34 | 35 | - 开箱即用, 也可以进行定制化改造。 36 | - 技术栈无关,任意技术栈的应用均可 `使用/接入`,不论是 React/Vue/Angular/Svelte/JQuery 还是其他等框架。 37 | - 共享依赖,同样的依赖只需要加载一次,减少不必要的体积与打包时间 38 | - 像调用原生组件一样进行插件间的依赖调用 39 | - 基于 `Rollup` 进行打包,快! 40 | - 为现代前端而生。在过去,我们通过window暴露方法,现在我们所有的代码都需要编译成模块,而暴露也通过模块 41 | - 拓扑依赖排序,防止时序性的问题。 42 | 43 | ## 文档 44 | 45 | - [官方文档](https://ministar.moonrailgun.com/) 46 | - [Github](https://github.com/moonrailgun/mini-star) 47 | - [微内核架构在大型前端系统中的应用(微前端)](https://segmentfault.com/a/1190000016862735) 48 | -------------------------------------------------------------------------------- /website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/tutorial/quickstart/createPlugin.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 2 3 | --- 4 | 5 | # 创建插件 6 | 7 | 我们可以通过`cli`快速创建一个新的插件。 8 | 9 | ```bash 10 | npx mini-star createPlugin 11 | ``` 12 | 13 | `mini-star` 会通过一个交互式终端程序询问一些问题, 以最后创建一个插件。 14 | 15 | 插件会自动放在根目录的 `plugins/` 目录下。 16 | 17 | 现在,我们尝试创建一个名为 `test` 的插件: 18 | 19 | ![](/img/docs/createPlugin.jpg) 20 | 21 | 此时 `/plugins` 目录应当如下: 22 | ``` 23 | ./plugins 24 | └── test 25 | ├── package.json 26 | ├── src 27 | │   └── index.ts 28 | └── tsconfig.json 29 | ``` 30 | 31 | 一个最简单的插件完成了,让我们以 `index.ts` 为入口开始你的插件之旅 32 | 33 | ## 编译插件 34 | 35 | 为了使插件能够被正确加载, 每次修改好之后别忘了使用ministar编译插件代码。默认会被打包输出到当前目录的`dist/plugins`目录下。 36 | 37 | 你也可以通过修改配置文件的 [outDir](../guide/ministarrc#outdir) 来修改输出。 38 | 39 | ```bash 40 | npx mini-star buildPlugin test 41 | 42 | # or 43 | 44 | npx mini-star buildPlugin all 45 | ``` 46 | -------------------------------------------------------------------------------- /website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/tutorial/quickstart/install.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | --- 4 | 5 | # 安装 6 | 7 | 安装 `mini-star` 非常简单,仅需一步即可完成 8 | 9 | ```bash npm2yarn 10 | npm install mini-star 11 | ``` 12 | 13 | mini-star 由三个部分组成: `runtime`, `bundler` 和 `cli` 14 | 15 | 我们来检查一下 `mini-star` 的`cli`是否正常工作。 16 | 17 | ```bash 18 | npx mini-star --help 19 | ``` 20 | 21 | 让我们快速进入[下一步](./createPlugin)。 22 | -------------------------------------------------------------------------------- /website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/tutorial/quickstart/usePlugin.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # 加载插件 6 | 7 | 基座项目可以是任何一个项目,就像一个正常的项目一样。 8 | 9 | 只需要在合适的地方去通过`mini-star`来初始化项目即可 10 | 11 | 比如: 12 | ```javascript 13 | import { initMiniStar } from 'mini-star'; 14 | 15 | console.log('App start'); 16 | 17 | initMiniStar({ 18 | plugins: [ 19 | { 20 | name: 'test', 21 | url: '/plugins/test/index.js', // NOTICE: 请确保该地址可以被访问 22 | }, 23 | ], 24 | }).then(() => { 25 | console.log('Plugin Load Success'); 26 | }); 27 | ``` 28 | 29 | `plugins`中的列表可以来源于任何地方,本教程为了方便直接给出固定的一个数组,在实际生产环境中可以来自一个`App Store`或者用户的手动选择。 30 | 31 | 打包插件和基座项目后,此时输出的结构树应该是这样的: 32 | 33 | ``` 34 | . 35 | ├── bundle.js 36 | ├── bundle.js.map 37 | ├── index.html 38 | └── plugins 39 | └── test 40 | ├── index.js 41 | └── index.js.map 42 | ``` 43 | 44 | 启动一个静态文件服务(比如`http-server`)看一下。 45 | 46 | 控制台输出: 47 | ![](/img/docs/usePlugin.jpg) 48 | 49 | - 第一行来自基座项目第一个`console.log` 50 | - 第二行的 `Hello World` 来自`test` 插件的内容 51 | - 第三行来自基座项目插件加载完毕的回调 52 | 53 | 可以看见,我们非常简单的就为一个已有项目增加了插件的功能。`mini-star`就是如此简单! 54 | 55 | ## 特殊标记 56 | - `@capital/*` 基座项目导出的依赖 57 | - `@plugins/*` 插件项目导出的依赖 58 | -------------------------------------------------------------------------------- /website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/tutorial/why.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 3 3 | --- 4 | 5 | # 为什么是MiniStar 6 | 7 | `MiniStar`是一个简单的且开源的前端渐进式插件化框架。 8 | 9 | 它提供了一套完整的`cli/runtime/bundler`开发链路。如果想要定制化,也非常简单。而且MiniStar的设计是渐进式的, 就算是已有的项目也能一点点进行插件化进程而无需一次完成。 10 | 11 | ## 与小程序的区别 12 | 13 | `MiniStar`和`小程序`比较像,因为他们都是将部分功能作为独立的子项目进行开发与部署。区别在于`MiniStar`与主程序的通讯更加紧密,复用的内容更加多。且没有复杂的运行时。 14 | 15 | `小程序`更加适合对于安全场景更加高的场合,而`MiniStar`的插件则更加信任插件代码。 16 | 17 | 另外小程序更加适合`app`, 而`MiniStar`则是为`web`而生 18 | 19 | ## 与qiankun的区别 20 | 21 | `MiniStar`是一款微内核框架,`qiankun`则是一款微前端框架 22 | 23 | `MiniStar`更加注重依赖的复用与主应用能力的提供,更加强调技术栈的同构性 24 | 25 | `qiankun`是为了解决多个异构子应用在同一个主应用运行的场景。更加注重依赖与代码的隔离 26 | 27 | 他们是两种解决方案,解决的问题是不一样的。 28 | 29 | 如果是在寻求微前端的解决方案,可以看下 [`qiankun`](https://qiankun.umijs.org/) 30 | 31 | ## 与requirejs的区别 32 | 33 | `requirejs`的实现与`MiniStar`非常像。 34 | 35 | 但是`requirejs`的最小组合单位是文件 36 | 37 | 而`MiniStar`的最小组合单位是模块。 38 | 39 | `MiniStar`更加符合现代前端的模块思想,而且不会对现有的代码习惯造成影响。 40 | -------------------------------------------------------------------------------- /website/i18n/zh-Hans/docusaurus-theme-classic/footer.json: -------------------------------------------------------------------------------- 1 | { 2 | "link.title.Docs": { 3 | "message": "文档", 4 | "description": "The title of the footer links column with title=Docs in the footer" 5 | }, 6 | "link.title.Community": { 7 | "message": "社区", 8 | "description": "The title of the footer links column with title=Community in the footer" 9 | }, 10 | "link.title.More": { 11 | "message": "更多", 12 | "description": "The title of the footer links column with title=More in the footer" 13 | }, 14 | "link.item.label.Tutorial": { 15 | "message": "教程", 16 | "description": "The label of footer link with label=Tutorial linking to /docs/tutorial/intro" 17 | }, 18 | "copyright": { 19 | "message": "Copyright © 2021 My Project, Inc. Built with Docusaurus.", 20 | "description": "The footer copyright" 21 | }, 22 | "link.item.label.GitHub": { 23 | "message": "GitHub", 24 | "description": "The label of footer link with label=GitHub linking to https://github.com/moonrailgun/mini-star" 25 | } 26 | } -------------------------------------------------------------------------------- /website/i18n/zh-Hans/docusaurus-theme-classic/navbar.json: -------------------------------------------------------------------------------- 1 | { 2 | "item.label.Tutorial": { 3 | "message": "教程", 4 | "description": "Navbar item with label Tutorial" 5 | }, 6 | "item.label.Blog": { 7 | "message": "博客", 8 | "description": "Navbar item with label Blog" 9 | }, 10 | "item.label.GitHub": { 11 | "message": "GitHub", 12 | "description": "Navbar item with label GitHub" 13 | }, 14 | "title": { 15 | "message": "MiniStar", 16 | "description": "The title in the navbar" 17 | } 18 | } -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start -p 3400 --no-open", 8 | "start:zh": "docusaurus start -p 3400 --locale zh-Hans --no-open", 9 | "build:docs": "docusaurus build", 10 | "build:speech": "cd ./speech/intro && yarn run build && node ../../scripts/moveSpeech.js", 11 | "build": "yarn run build:speech && yarn run build:docs", 12 | "swizzle": "docusaurus swizzle", 13 | "deploy": "docusaurus deploy", 14 | "clear": "docusaurus clear", 15 | "serve": "docusaurus serve", 16 | "write-translations": "docusaurus write-translations", 17 | "write-translations:zh": "docusaurus write-translations --locale zh-Hans", 18 | "write-heading-ids": "docusaurus write-heading-ids", 19 | "postinstall": "cd ./speech/intro && yarn" 20 | }, 21 | "dependencies": { 22 | "@docusaurus/core": "2.0.0-beta.21", 23 | "@docusaurus/preset-classic": "2.0.0-beta.21", 24 | "@docusaurus/remark-plugin-npm2yarn": "^2.0.0-beta.21", 25 | "@mdx-js/react": "^1.6.21", 26 | "@svgr/webpack": "^5.5.0", 27 | "clsx": "^1.1.1", 28 | "file-loader": "^6.2.0", 29 | "fs-extra": "^10.0.0", 30 | "react": "^17.0.1", 31 | "react-dom": "^17.0.1", 32 | "url-loader": "^4.1.1" 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.5%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | }, 46 | "devDependencies": { 47 | "@docusaurus/module-type-aliases": "^2.0.0-beta.21", 48 | "@tsconfig/docusaurus": "^1.0.2", 49 | "@types/react": "^17.0.9", 50 | "@types/react-helmet": "^6.1.1", 51 | "@types/react-router-dom": "^5.1.7", 52 | "typescript": "^4.3.2" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /website/scripts/moveSpeech.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs-extra'); 3 | 4 | fs.ensureDirSync(path.join(__dirname, '../static/speech')); 5 | fs.moveSync( 6 | path.join(__dirname, '../speech/intro/dist'), 7 | path.join(__dirname, '../static/speech/intro'), 8 | { 9 | overwrite: true, 10 | } 11 | ); 12 | -------------------------------------------------------------------------------- /website/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | module.exports = { 13 | // By default, Docusaurus generates a sidebar from the docs folder structure 14 | tutorial: [{ type: 'autogenerated', dirName: 'tutorial' }], 15 | 16 | // But you can create a sidebar manually 17 | /* 18 | tutorialSidebar: [ 19 | { 20 | type: 'category', 21 | label: 'Tutorial', 22 | items: ['hello'], 23 | }, 24 | ], 25 | */ 26 | }; 27 | -------------------------------------------------------------------------------- /website/speech/intro/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | *.local 5 | index.html 6 | .remote-assets 7 | components.d.ts 8 | -------------------------------------------------------------------------------- /website/speech/intro/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /website/speech/intro/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to [Slidev](https://github.com/slidevjs/slidev)! 2 | 3 | To start the slide show: 4 | 5 | - `npm install` 6 | - `npm run dev` 7 | - visit http://localhost:3030 8 | 9 | Edit the [slides.md](./slides.md) to see the changes. 10 | 11 | Learn more about Slidev on [documentations](https://sli.dev/). 12 | -------------------------------------------------------------------------------- /website/speech/intro/netlify.toml: -------------------------------------------------------------------------------- 1 | [build.environment] 2 | NODE_VERSION = "14" 3 | 4 | [build] 5 | publish = "dist" 6 | command = "npm run build" 7 | 8 | [[redirects]] 9 | from = "/*" 10 | to = "/index.html" 11 | status = 200 12 | -------------------------------------------------------------------------------- /website/speech/intro/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "slidev --open", 5 | "build": "slidev build --base /speech/intro/", 6 | "export": "slidev export" 7 | }, 8 | "dependencies": { 9 | "@slidev/cli": "^0.34.1", 10 | "@slidev/theme-default": "*", 11 | "@slidev/theme-seriph": "*" 12 | }, 13 | "name": "intro" 14 | } 15 | -------------------------------------------------------------------------------- /website/speech/intro/public/chrome.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonrailgun/mini-star/642e992dd6ad49b7b2441a0f9eed7ceb7fa5e793/website/speech/intro/public/chrome.jpeg -------------------------------------------------------------------------------- /website/speech/intro/public/vscode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moonrailgun/mini-star/642e992dd6ad49b7b2441a0f9eed7ceb7fa5e793/website/speech/intro/public/vscode.png -------------------------------------------------------------------------------- /website/speech/intro/slides.md: -------------------------------------------------------------------------------- 1 | --- 2 | theme: seriph 3 | background: https://source.unsplash.com/collection/94734566/1920x1080 4 | class: text-center 5 | highlighter: shiki 6 | lineNumbers: false 7 | routerMode: 'hash' 8 | monaco: true # default "dev" 9 | info: > 10 | ## Mini-star 11 | 12 | Pluginize your project 13 | 14 | Learn more at 15 | [https://ministar.moonrailgun.com/](https://ministar.moonrailgun.com/) 16 | title: Mini-Star 17 | --- 18 | 19 | 20 | 21 | # Mini-Star 22 | 23 | Pluginize your project 24 | 25 |
26 | 27 | Start your Star Tour 28 | 29 |
30 | 31 |
32 | 34 | 35 | 36 |
37 | 38 | 41 | 42 | --- 43 | 44 | # Frist of All: 什么是插件 45 | 46 |
47 | 48 | 49 | 50 | --- 51 | 52 | # 为什么我们需要插件机制? 53 | 54 | ## 在 SaaS 领域中的应用 55 | 56 | - 业务组装 57 | - 不同租户环境搭建不同的插件组装 58 | - 客户二开 59 | 60 | ## 同构微前端场景 61 | 62 | 是的,微前端也是一种特殊的插件化。 63 | 64 | ## 提供插件的应用 65 | 66 |
67 | 68 | 69 |
70 | 71 | - 各种成熟的编辑器如: idea, vscode 72 | - Chrome 73 | - 宝塔面板 74 | 等等 75 | 76 | 77 | 88 | 89 | --- 90 | layout: image-right 91 | image: https://source.unsplash.com/collection/94734566/1920x1080?1 92 | --- 93 | 94 | # 以前我们是如何实现插件的 95 | 96 | 在古早以前,我们是如何让两段在编写时完全不知道对方的代码同时生效? 97 | 98 | ```html 99 | 100 | 101 | ``` 102 | 103 | 简单加载,然后呢?通过共同修改DOM, 或者共同从`window`上取值/赋值来达到插件的作用 104 | 105 | --- 106 | layout: image-left 107 | image: https://source.unsplash.com/collection/94734566/1920x1080?2 108 | --- 109 | 110 | # JQuery时代我们又是如何做的 111 | 112 | ```javascript 113 | $.fn.extend({ 114 | foo: function() { 115 | const el = $(this); 116 | // .... 117 | } 118 | }) 119 | 120 | $('#div').foo() 121 | ``` 122 | 123 | 通过`jQuery`提供的插件系统,我们有了闭包的概念,我们的插件不会像以前那样野蛮生长。 124 | 125 | 所有的`jQuery`插件能够共同依赖一个`jQuery`。因为`jQuery`是当时的唯一解 126 | 127 | --- 128 | 129 | # 现在,我们又该如何做 130 | 131 |
132 | 133 | 前端技术飞速发展的现在,我们面临无数选择:`React`, `Angular`, `Vue`, `Svelte`... 134 | 135 | 与此同时,我们又面临一个问题: 因为各种打包工具,我们的代码更加规范了,我们的代码都在自己的作用域中 —— 这不但代表了内部的变量不会污染外部,也同样意味着外部无法访问内部变量。唯一的公共空间就是 `window`。 136 | 137 | 这不优雅,也不安全。 138 | 139 | 140 | 141 |
142 |
143 |
144 | 145 | 而打包工具更是期望所有的资源都被知晓,而插件系统却是恰恰不期望被知道的存在 —— 他们应当在运行时组合,而不是编译时 146 | 147 | --- 148 | 149 | # 那么, 我们需要什么 150 | 151 |
152 | 153 | 前端不比后端,我们要实现插件机制需要面对比后端更多的挑战: 154 | 155 | - 无法动态加载, 前端只能通过正向的引用才能知道一个资源是否存在。而后端可以直接通过文件系统进行检查 156 | - 前端对性能有要求,而后端可以一股脑把所有的资源全都加载 157 | 158 |
159 |
160 | 161 | 我们需要: 162 | 163 | - 能够自动加载,并能够对性能进行一部分优化(比如异步加载/按需加载) 164 | - 有一定安全性,但是又能在各个module中进行通信 165 | - 能解决一定的时序性问题,防止同步加载的同时可能存在的依赖问题 166 | - 最好能够减少一些特殊的magic,减少开发中的异构感。 167 | 168 | --- 169 | 170 | # MiniStar 是什么? 171 | 172 | `MiniStar` 是一个为实现项目微内核(插件化)前端库,旨在帮助大家能更简单、无痛的构建(或改造成)一个生产可用微内核(插件化)架构系统。 173 | 174 | - 📦 **开箱即用** - `MiniStar` 提供了一套自洽的使用方法论,可以开箱即用,也可进行一定配置 175 | - 🏢 **工具完善** - `MiniStar` 拥有一套完整的开发工具链: 从 cli 到 bundler 到 loader,支持插件开发的全流程 176 | - 🎨 **代码精简** - `MiniStar` 类似于 `Redux`, 提供的更多是一套方法论而不是一套大而全的工具。精简意味着了引入成本低、改造成本低,可以根据实际情况进行调整 177 | - 🌏 **技术无关** - 不论是何种框架,`MiniStar`只依赖于最基础的 vanilla js。 178 | - 🎥 **依赖共享** - 节约应用大小与加载时间,不必要的代码我们不打包两遍 179 | - ✈️ **站在巨人肩膀上** - 打包基于`Rollup`, 打包插件快而简单, 并且可以直接接入现成的生态系统。 180 | - 🎯 **为现代工程而生** - 基于`module`形式的引入,开发中几乎无法感觉到区别 181 | 182 |
183 |
184 | 185 | 更多介绍 [官方文档](https://ministar.moonrailgun.com/zh-Hans/docs/tutorial/intro) 186 | 187 | 191 | 192 | 203 | 204 | --- 205 | 206 | # 真实世界的一个插件示例 207 | 208 | ```ts {all|1|2|18-24|all} 209 | import React from 'react'; 210 | import { regGroupPanel, useCurrentGroupPanelInfo } from '@capital/common'; 211 | 212 | const PLUGIN_NAME = 'com.msgbyte.webview'; 213 | 214 | const GroupWebPanelRender = () => { 215 | const groupPanelInfo = useCurrentGroupPanelInfo(); 216 | 217 | if (!groupPanelInfo) { 218 | return
加载失败, 面板信息不存在
; 219 | } 220 | 221 | return ( 222 |