├── .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 | 
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 |
2 | This Component Render from Plugin
3 |
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 |
11 |
12 |
13 |
14 |
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 |
12 | {{ msg }}
13 |
14 |
15 | Recommended IDE setup:
16 | VSCode
17 | +
18 | Volar
19 |
20 |
21 |
22 |
23 | Vite Documentation
24 |
25 | |
26 | Vue 3 Documentation
27 |
28 |
29 |
30 |
31 | Edit
32 | components/HelloWorld.vue
to test hot module replacement.
33 |
34 |
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 |
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 |
223 | );
224 | };
225 |
226 | regGroupPanel({
227 | name: `${PLUGIN_NAME}/grouppanel`,
228 | label: '网页面板',
229 | provider: PLUGIN_NAME,
230 | extraFormMeta: [{ type: 'text', name: 'url', label: '网址' }],
231 | render: GroupWebPanelRender,
232 | });
233 | ```
234 |
235 |
246 |
247 | ---
248 |
249 | # 开发过程
250 |
251 | ```mermaid {scale: 1.5}
252 | flowchart LR
253 | cli创建插件 --> 开发插件 --> 编译插件 --> 主应用加载插件
254 | ```
255 |
256 | ```bash
257 | ministar createPlugin
258 | ministar buildPlugin
259 | ```
260 |
261 | 主应用提供可注入的接口,并把注入函数暴露出来。插件调用注入函数实现具体实现。
262 |
263 | 在这个过程中,主应用是对插件实现是完全无感知的。这个过程就是我们常说的[控制反转](https://zh.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC)
264 |
265 |
266 | ---
267 | layout: image-right
268 | image: https://source.unsplash.com/collection/94734566/1920x1080?3
269 | ---
270 |
271 | # MiniStar 的设计哲学
272 |
273 |
274 |
275 | `MiniStar` 并不是想要提供一套完整的、严格的框架来限制开发者的使用。她只提供一个最基础的实现来帮助开发者完成最基本的需求: 即模块之间的通讯
276 |
277 | 至于具体做什么,`MiniStar`并不关心,开发者可以用任意他们所能想得到的方式来使用`MiniStar`
278 |
279 | 就和`Redux`一样,任意框架,任意架构,都可以使用。不设任何限制,并且足够小,可以渐进式对自己的项目进行插件化改造。
280 |
281 | ---
282 |
283 | # 实际代码
284 |
285 |
286 |
287 |
288 |
289 | ### 主应用
290 |
291 | ```js {monaco}
292 | import { regDependency, regSharedModule, initMiniStar } from 'mini-star';
293 |
294 | export function initPlugins() {
295 | regDependency('react', () => import('react'));
296 | regSharedModule('@capital/common', () => import('./common/index'));
297 |
298 | const plugins = [{
299 | name: 'foo',
300 | url: '/plugins/foo/index.js'
301 | }, {
302 | name: 'bar',
303 | url: '/plugins/bar/index.js'
304 | }]
305 |
306 | return initMiniStar({
307 | plugins,
308 | });;
309 | }
310 | ```
311 |
312 |
313 |
314 |
315 |
316 | ### 插件
317 |
318 | ```js {monaco}
319 | import React from 'react'
320 | import { foo } from '@capital/common';
321 |
322 | foo(
aaa
)
323 | ```
324 |
325 | ### 插件依赖配置
326 |
327 |
.ministarrc.json
328 |
329 | ```json {monaco}
330 | {
331 | "externalDeps": ["react"]
332 | }
333 | ```
334 |
335 |
336 |
337 |
338 |
339 | ---
340 |
341 | # 相比于微前端框架的优势
342 |
343 | 讲到这里,相信大家都会觉得微内核与微前端有共通之处。
344 |
345 | 微内核如`MiniStar` 相较于 微前端如`qiankun`/`garfish` 等,两者本质上是为了解决不同场景而存在的。但是在某些情况下他们是有共同点的。
346 |
347 | 比如他们都由一个基座项目,以及若干个子项目组成。
348 |
349 | 而在某些意义上,微内核架构可以理解为微前端架构的一个超集。
350 |
351 |
352 | ---
353 |
354 | # 微内核对比微前端
355 |
356 | | | MiniStar | qiankun |
357 | | ------- | ---- | --- |
358 | | 项目重点 | 以主项目作为核心业务,插件机制提供额外能力,类似`vscode`/`chrome` | 以子应用为核心业务,基座项目提供路由分发能力,适用于后台管理系统 |
359 | | 打包方式 | 使用`ministar-bundler`基于`rollup`进行打包 | 任意打包方式均可 |
360 | | 共享依赖 | 使用`module`方式进行共享,并支持懒加载 | 使用`external`进行共享 |
361 | | 多子应用 | 多子应用共存 | 偏向于单子应用 |
362 | | 适用场景 | 插件化应用 | 路由化应用(后台管理平台) |
363 |
364 | ---
365 | layout: center
366 | class: text-center
367 | ---
368 |
369 | # 了解更多
370 |
371 | [文档](https://ministar.moonrailgun.com/) · [GitHub](https://github.com/moonrailgun/mini-star)
372 |
--------------------------------------------------------------------------------
/website/speech/intro/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "rewrites": [
3 | { "source": "/(.*)", "destination": "/index.html" }
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/website/src/components/HomepageFeatures.module.css:
--------------------------------------------------------------------------------
1 | /* stylelint-disable docusaurus/copyright-header */
2 |
3 | .features {
4 | display: flex;
5 | align-items: center;
6 | padding: 2rem 0;
7 | width: 100%;
8 | }
9 |
10 | .featureSvg {
11 | height: 200px;
12 | width: 200px;
13 | }
14 |
--------------------------------------------------------------------------------
/website/src/components/HomepageFeatures.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prop-types */
2 | /* eslint-disable @typescript-eslint/no-var-requires */
3 | import React from 'react';
4 | import clsx from 'clsx';
5 | import Translate, { translate } from '@docusaurus/Translate';
6 | import styles from './HomepageFeatures.module.css';
7 |
8 | interface FeatureItemType {
9 | title: string;
10 | description: React.ReactNode;
11 | Svg: React.ElementType;
12 | }
13 |
14 | const FeatureList: FeatureItemType[] = [
15 | {
16 | title: translate({
17 | message: 'Start with MiniStar',
18 | }),
19 | Svg: require('../../static/img/undraw_Dev_focus_re_6iwt.svg').default,
20 | description: (
21 | MiniStar }}
24 | >
25 | {
26 | 'Start your project with {title} or inject it in existed project. Its Progressive!'
27 | }
28 |
29 | ),
30 | },
31 | {
32 | title: translate({
33 | message: 'Pluginize your Project',
34 | }),
35 | Svg: require('../../static/img/undraw_Mobile_marketing_re_p77p.svg')
36 | .default,
37 | description: (
38 |
39 | Decoupling is very important part for Engineering. Dynamic code help you
40 | split your project, so its can independent development and independent
41 | deployment.
42 |
43 | ),
44 | },
45 | {
46 | title: translate({
47 | message: 'For Any Framework',
48 | }),
49 | Svg: require('../../static/img/undraw_JavaScript_frameworks_8qpc.svg')
50 | .default,
51 | description: (
52 | MiniStar }}
55 | >
56 | {
57 | '{title} is Frame-independent framework, whether react, vue, svelte, jquery, or angular. You can feel free and at ease for that: its create for modern web project.'
58 | }
59 |
60 | ),
61 | },
62 | ];
63 |
64 | const Feature: React.FC = ({ Svg, title, description }) => (
65 |
66 |
67 |
68 |
69 |
70 |
{title}
71 |
{description}
72 |
73 |
74 | );
75 | Feature.displayName = 'Feature';
76 |
77 | export default function HomepageFeatures() {
78 | return (
79 |
80 |
81 |
82 | {FeatureList.map((props, idx) => (
83 |
84 | ))}
85 |
86 |
87 |
88 | );
89 | }
90 |
--------------------------------------------------------------------------------
/website/src/css/custom.css:
--------------------------------------------------------------------------------
1 | /* stylelint-disable docusaurus/copyright-header */
2 | /**
3 | * Any CSS included here will be global. The classic template
4 | * bundles Infima by default. Infima is a CSS framework designed to
5 | * work well for content-centric websites.
6 | */
7 |
8 | /* You can override the default Infima variables here. */
9 | :root {
10 | --ifm-color-primary: #4867b1;
11 | --ifm-color-primary-dark: #415d9f;
12 | --ifm-color-primary-darker: #3d5896;
13 | --ifm-color-primary-darkest: #32487c;
14 | --ifm-color-primary-light: #5775bb;
15 | --ifm-color-primary-lighter: #607cbe;
16 | --ifm-color-primary-lightest: #7b92c9;
17 |
18 | --ifm-code-font-size: 95%;
19 | }
20 |
21 | .docusaurus-highlight-code-line {
22 | background-color: rgb(72, 77, 91);
23 | display: block;
24 | margin: 0 calc(-1 * var(--ifm-pre-padding));
25 | padding: 0 var(--ifm-pre-padding);
26 | }
27 |
--------------------------------------------------------------------------------
/website/src/pages/index.module.css:
--------------------------------------------------------------------------------
1 | /* stylelint-disable docusaurus/copyright-header */
2 |
3 | /**
4 | * CSS files with the .module.css suffix will be treated as CSS modules
5 | * and scoped locally.
6 | */
7 |
8 | .heroBanner {
9 | padding: 4rem 0;
10 | text-align: center;
11 | position: relative;
12 | overflow: hidden;
13 | }
14 |
15 | @media screen and (max-width: 966px) {
16 | .heroBanner {
17 | padding: 2rem;
18 | }
19 | }
20 |
21 | .buttons {
22 | display: flex;
23 | align-items: center;
24 | justify-content: center;
25 | margin-top: 16px;
26 | }
27 |
28 | .headerCommand {
29 | margin: auto;
30 | margin-top: 1rem;
31 | max-width: 24rem;
32 | }
33 |
--------------------------------------------------------------------------------
/website/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import clsx from 'clsx';
3 | import Layout from '@theme/Layout';
4 | import Link from '@docusaurus/Link';
5 | import CodeBlock from '@theme/CodeBlock';
6 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
7 | import styles from './index.module.css';
8 | import HomepageFeatures from '../components/HomepageFeatures';
9 |
10 | function HomepageHeader() {
11 | const { siteConfig } = useDocusaurusContext();
12 | return (
13 |
31 | );
32 | }
33 |
34 | export default function Home() {
35 | const { siteConfig } = useDocusaurusContext();
36 | return (
37 |
41 |
42 |
43 |
44 |
45 |
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/website/src/pages/markdown-page.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Markdown page example
3 | ---
4 |
5 | # Markdown page example
6 |
7 | You don't need React to write simple standalone pages.
8 |
--------------------------------------------------------------------------------
/website/static/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moonrailgun/mini-star/642e992dd6ad49b7b2441a0f9eed7ceb7fa5e793/website/static/.nojekyll
--------------------------------------------------------------------------------
/website/static/img/docs/createPlugin.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moonrailgun/mini-star/642e992dd6ad49b7b2441a0f9eed7ceb7fa5e793/website/static/img/docs/createPlugin.jpg
--------------------------------------------------------------------------------
/website/static/img/docs/intro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moonrailgun/mini-star/642e992dd6ad49b7b2441a0f9eed7ceb7fa5e793/website/static/img/docs/intro.png
--------------------------------------------------------------------------------
/website/static/img/docs/usePlugin.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moonrailgun/mini-star/642e992dd6ad49b7b2441a0f9eed7ceb7fa5e793/website/static/img/docs/usePlugin.jpg
--------------------------------------------------------------------------------
/website/static/img/docusaurus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moonrailgun/mini-star/642e992dd6ad49b7b2441a0f9eed7ceb7fa5e793/website/static/img/docusaurus.png
--------------------------------------------------------------------------------
/website/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moonrailgun/mini-star/642e992dd6ad49b7b2441a0f9eed7ceb7fa5e793/website/static/img/favicon.ico
--------------------------------------------------------------------------------
/website/static/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moonrailgun/mini-star/642e992dd6ad49b7b2441a0f9eed7ceb7fa5e793/website/static/img/logo.png
--------------------------------------------------------------------------------
/website/static/img/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/website/static/img/tutorial/docsVersionDropdown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moonrailgun/mini-star/642e992dd6ad49b7b2441a0f9eed7ceb7fa5e793/website/static/img/tutorial/docsVersionDropdown.png
--------------------------------------------------------------------------------
/website/static/img/tutorial/localeDropdown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/moonrailgun/mini-star/642e992dd6ad49b7b2441a0f9eed7ceb7fa5e793/website/static/img/tutorial/localeDropdown.png
--------------------------------------------------------------------------------
/website/static/img/undraw_Dev_focus_re_6iwt.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/static/img/undraw_Mobile_marketing_re_p77p.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/static/img/undraw_docusaurus_tree.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/docusaurus/tsconfig.json",
3 | "include": ["src/"]
4 | }
5 |
--------------------------------------------------------------------------------