├── README.md ├── example ├── typescript │ ├── .gitignore │ ├── README.md │ ├── mocker │ │ ├── user.mock.js │ │ └── index.js │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ └── package.json ├── base │ ├── .gitignore │ ├── README.md │ ├── mocker │ │ ├── user.mock.js │ │ └── index.js │ └── package.json ├── express │ ├── .gitignore │ ├── README.md │ ├── index.js │ ├── mocker │ │ ├── user.mock.js │ │ └── index.js │ └── package.json ├── loadData │ ├── .gitignore │ ├── README.md │ ├── mocker │ │ ├── db │ │ │ ├── user.js │ │ │ └── userInfo.json │ │ └── index.js │ └── package.json ├── webpack │ ├── .gitignore │ ├── README.md │ ├── mocker │ │ ├── user.js │ │ └── index.js │ ├── package.json │ ├── public │ │ └── index.html │ ├── webpack.config.js │ └── src │ │ └── index.js └── create-react-app │ ├── README.md │ ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── index.html │ ├── mocker │ ├── db │ │ ├── user.js │ │ └── userInfo.js │ └── index.js │ ├── src │ ├── setupTests.js │ ├── App.test.js │ ├── index.js │ ├── setupProxy.js │ ├── index.css │ ├── App.css │ ├── App.js │ └── logo.svg │ ├── .gitignore │ ├── package.json │ └── .eslintcache ├── lerna.json ├── packages └── core │ ├── src │ ├── __test__ │ │ └── index.test.ts │ ├── utils.ts │ ├── delay.ts │ ├── proxyHandle.ts │ ├── mockerHandle.ts │ ├── bin │ │ └── mocker.ts │ └── index.ts │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── BUG_REPORT.md │ └── FEATURE_REQUEST.md └── workflows │ └── ci.yml ├── renovate.json ├── .gitignore ├── LICENSE ├── package.json └── README-zh.md /README.md: -------------------------------------------------------------------------------- 1 | packages/core/README.md -------------------------------------------------------------------------------- /example/typescript/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json -------------------------------------------------------------------------------- /example/base/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | package-lock.json -------------------------------------------------------------------------------- /example/express/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | package-lock.json -------------------------------------------------------------------------------- /example/loadData/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | package-lock.json -------------------------------------------------------------------------------- /example/webpack/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | package-lock.json -------------------------------------------------------------------------------- /example/create-react-app/README.md: -------------------------------------------------------------------------------- 1 | Mocker API Example 2 | --- 3 | 4 | ```bash 5 | npm run start 6 | ``` 7 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "3.0.2", 3 | "packages": [ 4 | "packages/*", 5 | "example/*" 6 | ] 7 | } -------------------------------------------------------------------------------- /example/create-react-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /example/create-react-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaywcjlove/mocker-api/HEAD/example/create-react-app/public/favicon.ico -------------------------------------------------------------------------------- /example/create-react-app/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaywcjlove/mocker-api/HEAD/example/create-react-app/public/logo192.png -------------------------------------------------------------------------------- /example/create-react-app/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaywcjlove/mocker-api/HEAD/example/create-react-app/public/logo512.png -------------------------------------------------------------------------------- /packages/core/src/__test__/index.test.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | describe('sum', () => { 4 | it('works', () => { 5 | expect(2).toEqual(2); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /example/typescript/README.md: -------------------------------------------------------------------------------- 1 | typescript 2 | === 3 | 4 | ```bash 5 | npm run watch 6 | npm run build 7 | ``` 8 | 9 | ```bash 10 | npm run start 11 | ``` -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: jaywcjlove 2 | buy_me_a_coffee: jaywcjlove 3 | custom: ["https://www.paypal.me/kennyiseeyou", "https://jaywcjlove.github.io/#/sponsor"] 4 | -------------------------------------------------------------------------------- /example/base/README.md: -------------------------------------------------------------------------------- 1 | Mocker API Example 2 | --- 3 | 4 | ```bash 5 | npm install 6 | npm run api 7 | ``` 8 | 9 | ## test 10 | 11 | ```bash 12 | curl http://localhost:3721/repos/hello 13 | ``` -------------------------------------------------------------------------------- /example/express/README.md: -------------------------------------------------------------------------------- 1 | Mocker API Example 2 | --- 3 | 4 | 5 | ```bash 6 | npm install 7 | npm run start 8 | ``` 9 | 10 | ## test 11 | 12 | ```bash 13 | curl http://localhost:8080/api/jobs 14 | ``` -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "packageRules": [ 6 | { 7 | "matchPackagePatterns": ["*"], 8 | "rangeStrategy": "replace" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /example/loadData/README.md: -------------------------------------------------------------------------------- 1 | Mocker API Example 2 | --- 3 | 4 | ```bash 5 | npm install 6 | npm run api 7 | ``` 8 | 9 | ## test 10 | 11 | ```bash 12 | curl http://localhost:3721/api/user 13 | curl http://localhost:3721/api/user/info 14 | ``` -------------------------------------------------------------------------------- /example/webpack/README.md: -------------------------------------------------------------------------------- 1 | Mocker API Example 2 | --- 3 | 4 | ```bash 5 | npm install 6 | npm run start 7 | open http://localhost:8082 8 | ``` 9 | 10 | ## test 11 | 12 | ```bash 13 | curl http://localhost:8080/api/userinfo/2342a-234ab-342 14 | ``` -------------------------------------------------------------------------------- /example/express/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const apiMocker = require('mocker-api'); 3 | 4 | const app = express(); 5 | 6 | apiMocker(app, require.resolve('./mocker/index')) 7 | app.listen(8080); 8 | console.log('=> http://localhost:8080') -------------------------------------------------------------------------------- /example/loadData/mocker/db/user.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (req, res) => { 3 | return res.json([ 4 | { 5 | id: 1, 6 | username: 'kenny', 7 | sex: 34 8 | }, { 9 | id: 2, 10 | username: 'mocker', 11 | sex: 23 12 | } 13 | ]); 14 | } -------------------------------------------------------------------------------- /example/create-react-app/mocker/db/user.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (req, res) => { 3 | return res.json([ 4 | { 5 | id: 1, 6 | username: 'kenny', 7 | sex: 34 8 | }, { 9 | id: 2, 10 | username: 'mocker', 11 | sex: 23 12 | } 13 | ]); 14 | } -------------------------------------------------------------------------------- /example/create-react-app/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /example/create-react-app/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /example/create-react-app/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); 12 | 13 | -------------------------------------------------------------------------------- /example/base/mocker/user.mock.js: -------------------------------------------------------------------------------- 1 | const BASE_URL = '/api/userinfo'; 2 | 3 | const release = { 4 | [`GET ${BASE_URL}/:id`]: (req, res) => { 5 | console.log('---->', req.params) 6 | return res.json({ 7 | id: 1, 8 | username: 'kenny', 9 | sex: 6 10 | }); 11 | } 12 | } 13 | 14 | module.exports = release -------------------------------------------------------------------------------- /example/express/mocker/user.mock.js: -------------------------------------------------------------------------------- 1 | const BASE_URL = '/api/userinfo'; 2 | 3 | const release = { 4 | [`GET ${BASE_URL}/:id`]: (req, res) => { 5 | console.log('---->', req.params) 6 | return res.json({ 7 | id: 1, 8 | username: 'kenny', 9 | sex: 6 10 | }); 11 | } 12 | } 13 | 14 | module.exports = release -------------------------------------------------------------------------------- /example/create-react-app/src/setupProxy.js: -------------------------------------------------------------------------------- 1 | const apiMocker = require('mocker-api'); 2 | const path = require('path'); 3 | 4 | module.exports = function(app) { 5 | apiMocker(app, path.resolve('./mocker/index.js'), { 6 | proxy: { 7 | '/repos/*path': 'https://api.github.com/', 8 | }, 9 | changeHost: true, 10 | }); 11 | }; -------------------------------------------------------------------------------- /example/typescript/mocker/user.mock.js: -------------------------------------------------------------------------------- 1 | const BASE_URL = '/api/userinfo'; 2 | 3 | const release = { 4 | [`GET ${BASE_URL}/:id`]: (req, res) => { 5 | console.log('---->', req.params) 6 | return res.json({ 7 | id: 1, 8 | username: 'kenny', 9 | sex: 6 10 | }); 11 | } 12 | } 13 | 14 | module.exports = release -------------------------------------------------------------------------------- /example/loadData/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@example/load-data", 3 | "version": "3.0.2", 4 | "description": "", 5 | "private": true, 6 | "scripts": { 7 | "api": "mocker ./mocker" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "devDependencies": { 12 | "mocker-api": "^3.0.2" 13 | }, 14 | "license": "MIT" 15 | } 16 | -------------------------------------------------------------------------------- /example/base/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@example/base", 3 | "version": "3.0.2", 4 | "description": "", 5 | "private": true, 6 | "scripts": { 7 | "api": "mocker ./mocker" 8 | }, 9 | "keywords": [], 10 | "mocker": { 11 | "port": 7788 12 | }, 13 | "author": "", 14 | "devDependencies": { 15 | "mocker-api": "^3.0.2" 16 | }, 17 | "license": "MIT" 18 | } 19 | -------------------------------------------------------------------------------- /example/create-react-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /example/create-react-app/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /example/typescript/src/index.ts: -------------------------------------------------------------------------------- 1 | import apiMocker from 'mocker-api'; 2 | import express from 'express'; 3 | import path from 'path'; 4 | 5 | const app = express(); 6 | 7 | // apiMocker(app, { 8 | // _proxy: { 9 | // changeHost: true, 10 | // }, 11 | // 'GET /api/user': { 12 | // id: 1, 13 | // sex: 0 14 | // } 15 | // }); 16 | 17 | apiMocker(app, path.resolve('./mocker/index.js')); 18 | app.listen(8080); 19 | 20 | 21 | console.log('=> http://localhost:8080') -------------------------------------------------------------------------------- /example/webpack/mocker/user.js: -------------------------------------------------------------------------------- 1 | exports.login = function (req, res) { 2 | const { password, username } = req.body; 3 | if (password === '888888' && username === 'admin') { 4 | return res.json({ 5 | status: 'ok', 6 | code: 0, 7 | token: "sdfsdfsdfdsf", 8 | data: { 9 | id: 1, 10 | username: 'kenny', 11 | sex: 6 12 | } 13 | }); 14 | } else { 15 | return res.json({ 16 | status: 'error', 17 | code: 403 18 | }); 19 | } 20 | } -------------------------------------------------------------------------------- /example/express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@example/express", 3 | "version": "3.0.2", 4 | "description": "", 5 | "private": true, 6 | "scripts": { 7 | "start": "nodemon index.js" 8 | }, 9 | "nodemonConfig": { 10 | "ignore": [ 11 | "node_modules/*" 12 | ] 13 | }, 14 | "author": "", 15 | "license": "MIT", 16 | "dependencies": { 17 | "express": "~4.21.0", 18 | "nodemon": "~2.0.20" 19 | }, 20 | "devDependencies": { 21 | "mocker-api": "^3.0.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/typescript/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "moduleResolution": "node", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "declaration": true, 14 | "experimentalDecorators": true, 15 | "baseUrl": "src", 16 | }, 17 | "include": ["src/**/*"] 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "resolveJsonModule": true, 5 | "esModuleInterop": true, 6 | "allowSyntheticDefaultImports": true, 7 | "declaration": true, 8 | "noImplicitAny": true, 9 | "strict": false, 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "sourceMap": true, 13 | "skipLibCheck": true, 14 | "outDir": "lib", 15 | "types": ["jest", "node"], 16 | "baseUrl": "." 17 | }, 18 | "include": ["src/**/*"] 19 | } 20 | -------------------------------------------------------------------------------- /example/loadData/mocker/index.js: -------------------------------------------------------------------------------- 1 | const delay = require('mocker-api/delay'); 2 | 3 | // 是否禁用代理 4 | const noProxy = process.env.NO_PROXY === 'true'; 5 | 6 | function loadData(data) { 7 | const result = {}; 8 | Object.keys(data).forEach((key) => { 9 | result[key] = require(data[key]); 10 | }); 11 | return result; 12 | } 13 | 14 | const proxy = loadData({ 15 | 'GET /api/user': './db/user', 16 | 'GET /api/user/info': './db/userInfo', 17 | 'OPTIONS /api/user/info': './db/userInfo', 18 | }); 19 | 20 | module.exports = (noProxy ? {} : delay(proxy, 1000)); 21 | -------------------------------------------------------------------------------- /example/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@example/typescript", 3 | "version": "3.0.2", 4 | "private": true, 5 | "description": "", 6 | "main": "index.js", 7 | "scripts": { 8 | "start": "node lib/index.js", 9 | "watch": "tsc -p ./ --types --outDir lib --watch", 10 | "build": "tsc -p ./ --types --outDir lib", 11 | "test": "tsc --noEmit" 12 | }, 13 | "author": "jaywcjlove", 14 | "license": "MIT", 15 | "dependencies": { 16 | "@types/express": "~4.17.14", 17 | "express": "~4.21.0", 18 | "mocker-api": "^3.0.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /example/webpack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@example/webpack", 3 | "version": "3.0.2", 4 | "private": true, 5 | "description": "Mocker API Example", 6 | "scripts": { 7 | "start": "webpack serve --progress --mode development", 8 | "build": "webpack --mode production" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "@webpack-cli/serve": "~2.0.1", 15 | "html-webpack-plugin": "~5.6.0", 16 | "mocker-api": "^3.0.2", 17 | "webpack": "^5.77.0", 18 | "webpack-cli": "^5.0.1", 19 | "webpack-dev-server": "^4.13.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /example/create-react-app/mocker/index.js: -------------------------------------------------------------------------------- 1 | const delay = require('mocker-api/delay'); 2 | 3 | // 是否禁用代理 4 | const noProxy = process.env.NO_PROXY === 'true'; 5 | 6 | function loadData(data) { 7 | const result = {}; 8 | Object.keys(data).forEach((key) => { 9 | result[key] = require(data[key]); 10 | }); 11 | return result; 12 | } 13 | 14 | const proxy = loadData({ 15 | 'GET /api/user': './db/user', 16 | 'GET /api/user/info': './db/userInfo', 17 | 'GET /api/userinfo/:id': './db/userInfo', 18 | 'OPTIONS /api/user/info': './db/userInfo', 19 | }); 20 | 21 | module.exports = (noProxy ? {} : delay(proxy, 1000)); 22 | -------------------------------------------------------------------------------- /example/create-react-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | lib 4 | node_modules 5 | npm-debug.log* 6 | coverage 7 | typedoc 8 | docs 9 | 10 | # misc 11 | .DS_Store 12 | .env.local 13 | .env.development.local 14 | .env.test.local 15 | .env.production.local 16 | 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | yarn.lock 21 | lerna-debug.log 22 | yarn-error.log 23 | package-lock.json 24 | 25 | .DS_Store 26 | .cache 27 | .vscode 28 | .idea 29 | .env 30 | 31 | *.mpassword 32 | *.bak 33 | *.tem 34 | *.temp 35 | #.swp 36 | *.*~ 37 | ~*.* 38 | 39 | # IDEA 40 | *.iml 41 | *.ipr 42 | *.iws 43 | .idea/ -------------------------------------------------------------------------------- /example/webpack/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | webpack api mocker 8 | 9 | 10 | Visit :/mocker/index.js to customize the API Try it? 11 |
12 |
id:
13 |
name:
14 |
age:
15 |
16 |
URL: /repos/jaywcjlove/github-rank:
17 |
URL: /repos/jaywcjlove/webpack-api-mocker:
18 | 19 | -------------------------------------------------------------------------------- /example/create-react-app/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG_REPORT.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Bug report 4 | about: File a report when something goes wrong so it can get fixed! 5 | title: 'Bug: ' 6 | 7 | --- 8 | 9 | 10 | 11 | **How To Reproduce** 12 | 13 | Node: x.x.x 14 | Webpack: x.x.x 15 | Mocker Api: x.x.x 16 | 17 | 1. ... 18 | 2. ... 19 | 20 | **Expected behavior** 21 | 22 | 23 | 24 | **Actual behavior** 25 | 26 | 27 | 28 | **Screenshots** 29 | 30 | 31 | 32 | **Additional context** 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /example/loadData/mocker/db/userInfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 2, 3 | "username": "jack_smith", 4 | "email": "jack@example.com", 5 | "name": "Jack Smith", 6 | "state": "blocked", 7 | "avatar_url": "http://localhost:3000/uploads/user/avatar/2/index.jpg", 8 | "web_url": "http://localhost:3000/jack_smith", 9 | "created_at": "2012-05-23T08:01:01Z", 10 | "is_admin": false, 11 | "bio": null, 12 | "location": null, 13 | "skype": "", 14 | "linkedin": "", 15 | "twitter": "", 16 | "website_url": "", 17 | "organization": "", 18 | "last_sign_in_at": null, 19 | "confirmed_at": "2012-05-30T16:53:06.148Z", 20 | "theme_id": 1, 21 | "last_activity_on": "2012-05-23", 22 | "color_scheme_id": 3, 23 | "projects_limit": 100, 24 | "current_sign_in_at": "2014-03-19T17:54:13Z", 25 | "identities": [], 26 | "can_create_group": true, 27 | "can_create_project": true, 28 | "two_factor_enabled": true, 29 | "external": false, 30 | "private_profile": false 31 | } -------------------------------------------------------------------------------- /example/create-react-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@example/create-react-app", 3 | "version": "3.0.2", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "~5.16.5", 7 | "@testing-library/react": "~14.0.0", 8 | "@testing-library/user-event": "~14.4.3", 9 | "react": "~18.2.0", 10 | "react-dom": "~18.2.0", 11 | "react-scripts": "~5.0.1" 12 | }, 13 | "devDependencies": { 14 | "mocker-api": "^3.0.2" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test", 20 | "eject": "react-scripts eject" 21 | }, 22 | "eslintConfig": { 23 | "extends": [ 24 | "react-app", 25 | "react-app/jest" 26 | ] 27 | }, 28 | "browserslist": { 29 | "production": [ 30 | ">0.2%", 31 | "not dead", 32 | "not op_mini all" 33 | ], 34 | "development": [ 35 | "last 1 chrome version", 36 | "last 1 firefox version", 37 | "last 1 safari version" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/webpack/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const apiMocker = require('mocker-api'); 4 | 5 | module.exports = { 6 | entry: { 7 | app: './src/index.js', 8 | }, 9 | output: { 10 | filename: '[name].bundle.js', 11 | path: path.resolve(__dirname, 'dist') 12 | }, 13 | devServer: { 14 | port: 8082, 15 | onBeforeSetupMiddleware: (devServer) => { 16 | apiMocker(devServer.app, path.resolve('./mocker/index.js'), { 17 | proxy: { 18 | '/repos/(.*)': 'https://api.github.com/', 19 | }, 20 | changeHost: true, 21 | }) 22 | 23 | }, 24 | // before(app){ 25 | // apiMocker(app, path.resolve('./mocker/index.js'), { 26 | // proxy: { 27 | // '/repos/(.*)': 'https://api.github.com/', 28 | // }, 29 | // changeHost: true, 30 | // }) 31 | // } 32 | }, 33 | plugins: [ 34 | new HtmlWebpackPlugin({ 35 | template: path.resolve('./public/index.html'), 36 | title: 'Webpack App Mocker API' 37 | }) 38 | ], 39 | }; -------------------------------------------------------------------------------- /packages/core/src/utils.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as toRegexp from 'path-to-regexp'; 3 | import type { PathToRegexpOptions, ParseOptions, Keys } from 'path-to-regexp'; 4 | 5 | const pathToRegexp = toRegexp.pathToRegexp; 6 | 7 | export function pathMatch(options: PathToRegexpOptions & ParseOptions) { 8 | options = options || {}; 9 | return function (path: string) { 10 | var keys: Keys = []; 11 | let regexpObject: RegExp | undefined = undefined 12 | try { 13 | var re = pathToRegexp(path, options); 14 | regexpObject = re.regexp; 15 | keys = re.keys; 16 | } catch (error) { 17 | console.error(error); 18 | } 19 | return function (pathname: string, params?: any) { 20 | var mData = regexpObject?.exec(pathname); 21 | if (!mData) return false; 22 | params = params || {}; 23 | var key, param; 24 | for (var i = 0; i < keys.length; i++) { 25 | key = keys[i]; 26 | param = mData[i + 1]; 27 | if (!param) continue; 28 | params[key.name] = decodeURIComponent(param); 29 | } 30 | return params; 31 | } 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 小弟调调™ 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 | -------------------------------------------------------------------------------- /example/create-react-app/mocker/db/userInfo.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = (req, res) => { 3 | return res.json({ 4 | "id": req.params.id ? req.params.id : 2, 5 | "username": "jack_smith", 6 | "email": "jack@example.com", 7 | "name": "Jack Smith", 8 | "state": "blocked", 9 | "avatar_url": "http://localhost:3000/uploads/user/avatar/2/index.jpg", 10 | "web_url": "http://localhost:3000/jack_smith", 11 | "created_at": "2012-05-23T08:01:01Z", 12 | "is_admin": false, 13 | "bio": null, 14 | "sex": '2', 15 | "location": null, 16 | "skype": "", 17 | "linkedin": "", 18 | "twitter": "", 19 | "website_url": "", 20 | "organization": "", 21 | "last_sign_in_at": null, 22 | "confirmed_at": "2012-05-30T16:53:06.148Z", 23 | "theme_id": 1, 24 | "last_activity_on": "2012-05-23", 25 | "color_scheme_id": 3, 26 | "projects_limit": 100, 27 | "current_sign_in_at": "2014-03-19T17:54:13Z", 28 | "identities": [], 29 | "can_create_group": true, 30 | "can_create_project": true, 31 | "two_factor_enabled": true, 32 | "external": false, 33 | "private_profile": false 34 | }); 35 | } -------------------------------------------------------------------------------- /example/webpack/src/index.js: -------------------------------------------------------------------------------- 1 | fetch('/api/userinfo/1314-sd', { 2 | 'Accept': 'application/json', 3 | 'Content-Type': 'application/x-www-form-urlencoded', 4 | }).then((response) => response.json()) 5 | .then(data => { 6 | console.log('data:', data); 7 | document.getElementById('name').innerHTML = data.username; 8 | document.getElementById('age').innerHTML = data.sex; 9 | document.getElementById('id').innerHTML = data.id; 10 | }); 11 | 12 | fetch('/repos/jaywcjlove/github-rank',) 13 | .then(response => response.json()) 14 | .then(data => { 15 | console.log('data:1', data) 16 | document.getElementById('mock').innerText = `from github api: webpack-api-mocker star count: ${data.stargazers_count}`; 17 | }); 18 | 19 | fetch('/repos/jaywcjlove/webpack-api-mocker') 20 | .then(response => response.json()) 21 | .then(data => { 22 | document.getElementById('github').innerText = `from github api: webpack-api-mocker star count: ${data.stargazers_count}` 23 | }); 24 | 25 | fetch('/api/login/account', { 26 | method: 'POST', 27 | headers: { 28 | 'Content-Type': 'application/json' 29 | }, 30 | body: JSON.stringify({ password: '888888', username: 'admin' }) 31 | }) -------------------------------------------------------------------------------- /example/create-react-app/.eslintcache: -------------------------------------------------------------------------------- 1 | [{"/Users/nihao/git-project/github/mocker-api/example/create-react-app/src/index.js":"1","/Users/nihao/git-project/github/mocker-api/example/create-react-app/src/App.js":"2","/Users/nihao/git-project/github/mocker-api/example/create-react-app/src/reportWebVitals.js":"3"},{"size":500,"mtime":1608791014252,"results":"4","hashOfConfig":"5"},{"size":1923,"mtime":1608792706897,"results":"6","hashOfConfig":"5"},{"size":362,"mtime":1608791014254,"results":"7","hashOfConfig":"5"},{"filePath":"8","messages":"9","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"10"},"7ndr3s",{"filePath":"11","messages":"12","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"13","messages":"14","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"10"},"/Users/nihao/git-project/github/mocker-api/example/create-react-app/src/index.js",[],["15","16"],"/Users/nihao/git-project/github/mocker-api/example/create-react-app/src/App.js",[],"/Users/nihao/git-project/github/mocker-api/example/create-react-app/src/reportWebVitals.js",[],{"ruleId":"17","replacedBy":"18"},{"ruleId":"19","replacedBy":"20"},"no-native-reassign",["21"],"no-negated-in-lhs",["22"],"no-global-assign","no-unsafe-negation"] -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | name: Feature request 4 | about: Suggest new feature for this project 5 | title: 'Feature: ' 6 | 7 | --- 8 | 9 | 10 | 11 | **Is your feature request related to a problem? Please describe.** 12 | 13 | 14 | 15 | **Describe the solution you'd like** 16 | 17 | 18 | 19 | **Describe alternatives you've considered** 20 | 21 | 22 | 23 | **Additional context** 24 | 25 | 26 | 27 | ### Issue Checklist 28 | 29 | 30 | 31 | - [ ] I have checked for other similar issues 32 | - [ ] I have explained why this change is important 33 | - [ ] I have added necessary documentation (if appropriate) 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mocker-api", 3 | "private": true, 4 | "description": "This is dev support mock RESTful API.", 5 | "workspaces": [ 6 | "packages/*", 7 | "example/*" 8 | ], 9 | "scripts": { 10 | "version": "lerna version --no-changelog --no-git-tag-version --no-push", 11 | "doc": "lerna exec --scope mocker-api -- typedoc src/index.ts src/delay.ts --name mocker-api --out docs/type", 12 | "build": "lerna exec --scope mocker-api -- tsbb build", 13 | "watch": "lerna exec --scope mocker-api -- tsbb watch", 14 | "test": "lerna exec --scope mocker-api -- tsbb test", 15 | "coverage": "lerna exec --scope mocker-api -- tsbb test --coverage", 16 | "example:base": "lerna exec --scope mocker-api -- npm run api", 17 | "example:create-react-app": "lerna exec --scope @example/create-react-app -- npm run build", 18 | "example:typescript": "lerna exec --scope @example/typescript -- npm run build", 19 | "example:webpack": "lerna exec --scope @example/webpack -- npm run build", 20 | "clean": "lerna clean --yes" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/jaywcjlove/mocker-api.git" 25 | }, 26 | "jest": {}, 27 | "engines": { 28 | "node": ">=16.0.0" 29 | }, 30 | "license": "MIT", 31 | "author": "Kenny Wong ", 32 | "markdown-to-html": { 33 | "reurls": { 34 | "README-zh.md": "index.zh.html", 35 | "README.md": "index.html" 36 | } 37 | }, 38 | "devDependencies": { 39 | "lerna": "^8.0.0", 40 | "tsbb": "^4.1.3", 41 | "typedoc": "~0.23.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/core/src/delay.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { MockerProxyRoute, MockerResult, MockerResultFunction } from './'; 3 | 4 | /** 5 | * You can use functional tool to enhance mock. [#17](https://github.com/jaywcjlove/webpack-api-mocker/issues/17) 6 | * 7 | * ```js 8 | * const delay = require('mocker-api/delay'); 9 | * const noProxy = process.env.NO_PROXY === 'true'; 10 | * 11 | * const proxy = { 12 | * 'GET /api/user': { 13 | * id: 1, 14 | * username: 'kenny', 15 | * sex: 6 16 | * }, 17 | * // ... 18 | * } 19 | * module.exports = (noProxy ? {} : delay(proxy, 1000)); 20 | * ``` 21 | */ 22 | 23 | module.exports = delay; 24 | 25 | export default function delay(proxy: MockerProxyRoute, timer: number = 0): MockerResult { 26 | const mockApi: MockerResult = {}; 27 | Object.keys(proxy).forEach((key) => { 28 | const result = proxy[key]; 29 | if ((Object.prototype.toString.call(result) === '[object String]' && /^http/.test(result as string)) || key === '_proxy' || timer === 0) { 30 | mockApi[key] = proxy[key]; 31 | } else { 32 | mockApi[key] = function (req: Request, res: Response) { 33 | let foo: MockerResultFunction; 34 | if (Object.prototype.toString.call(result) === '[object Function]') { 35 | foo = result as MockerResultFunction; 36 | } else { 37 | foo = (_req: Request, _res: Response) => { 38 | return _res.json(result); 39 | }; 40 | } 41 | setTimeout(() => { 42 | foo(req, res); 43 | }, timer); 44 | }; 45 | } 46 | }); 47 | 48 | return mockApi; 49 | } 50 | -------------------------------------------------------------------------------- /packages/core/src/proxyHandle.ts: -------------------------------------------------------------------------------- 1 | import URL from 'url'; 2 | import httpProxy from 'http-proxy'; 3 | import { Request, Response } from 'express'; 4 | import color from 'colors-cli/safe'; 5 | import { MockerOption, HttpProxyListeners } from '.'; 6 | 7 | export function proxyHandle(req: Request, res: Response, options: MockerOption = {}, proxyKey: string) { 8 | const currentProxy = options.proxy[proxyKey]; 9 | const url = URL.parse(currentProxy); 10 | if (options.changeHost) { 11 | req.headers.host = url.host; 12 | } 13 | const { options: proxyOptions = {}, listeners: proxyListeners = {} as HttpProxyListeners }: MockerOption['httpProxy'] = options.httpProxy; 14 | /** 15 | * rewrite target's url path. Object-keys will be used as RegExp to match paths. 16 | * https://github.com/jaywcjlove/mocker-api/issues/62 17 | */ 18 | Object.keys(options.pathRewrite).forEach(rgxStr => { 19 | const rePath = req.path.replace(new RegExp(rgxStr), options.pathRewrite[rgxStr]); 20 | const currentPath = [rePath]; 21 | if (req.url.indexOf('?') > 0) { 22 | currentPath.push(req.url.replace(/(.*)\?/, '')); 23 | } 24 | req.query = URL.parse(req.url, true).query; 25 | req.url = req.originalUrl = currentPath.join('?'); 26 | }); 27 | 28 | const proxyHTTP = httpProxy.createProxyServer({}); 29 | proxyHTTP.on('error', (err) => { 30 | console.error(`${color.red_b.black(` Proxy Failed: ${err.name}`)} ${err.message || ''} ${err.stack || ''} !!`); 31 | }); 32 | Object.keys(proxyListeners).forEach(event => { 33 | proxyHTTP.on(event, proxyListeners[event]); 34 | }); 35 | 36 | proxyHTTP.web(req, res, Object.assign({ target: url.href }, proxyOptions)); 37 | } -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mocker-api", 3 | "version": "3.0.2", 4 | "description": "This is dev support mock RESTful API.", 5 | "homepage": "https://wangchujiang.com/mocker-api/", 6 | "funding": "https://jaywcjlove.github.io/#/sponsor", 7 | "bin": { 8 | "mocker": "lib/bin/mocker.js" 9 | }, 10 | "main": "lib/index.js", 11 | "typings": "lib/index.d.ts", 12 | "exports": { 13 | ".": "./lib/index.js", 14 | "./delay": "./lib/delay.js", 15 | "./lib/delay": "./lib/delay.js" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/jaywcjlove/mocker-api.git" 20 | }, 21 | "jest": {}, 22 | "license": "MIT", 23 | "author": "Kenny Wong ", 24 | "files": [ 25 | "src", 26 | "lib" 27 | ], 28 | "engines": { 29 | "node": ">=16.0.0" 30 | }, 31 | "keywords": [ 32 | "express", 33 | "webpack-dev-server", 34 | "webpack-api-mocker", 35 | "webpack", 36 | "mocker-api", 37 | "mock", 38 | "mocker", 39 | "api", 40 | "fetch", 41 | "react", 42 | "create-react-app", 43 | "TypeScript" 44 | ], 45 | "dependencies": { 46 | "body-parser": "~1.20.1", 47 | "chokidar": "~4.0.0", 48 | "clear-module": "~4.1.2", 49 | "colors-cli": "~1.0.28", 50 | "detect-port": "~1.6.0", 51 | "express": "~4.21.0", 52 | "http-proxy": "~1.18.1", 53 | "local-ip-url": "~1.0.3", 54 | "minimist": "~1.2.7", 55 | "path-to-regexp": "~8.2.0" 56 | }, 57 | "devDependencies": { 58 | "@types/body-parser": "~1.19.2", 59 | "@types/detect-port": "~1.3.2", 60 | "@types/express": "~4.17.14", 61 | "@types/http-proxy": "~1.17.9", 62 | "@types/minimist": "~1.2.2", 63 | "@types/node": "~18.14.0" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /example/create-react-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /example/create-react-app/src/App.js: -------------------------------------------------------------------------------- 1 | import logo from './logo.svg'; 2 | import './App.css'; 3 | import { useEffect, useState } from 'react'; 4 | 5 | function App() { 6 | const [data, setData] = useState({}); 7 | const [githubRank, setGithubRank] = useState({}); 8 | const [apiMocker, setApiMocker] = useState({}); 9 | useEffect(() => { 10 | fetch('/api/userinfo/1314-sd', { 11 | 'Accept': 'application/json', 12 | 'Content-Type': 'application/x-www-form-urlencoded', 13 | }).then((response) => response.json()) 14 | .then(data => { 15 | setData(data); 16 | console.log('data:', data); 17 | // document.getElementById('name').innerHTML = data.username; 18 | // document.getElementById('age').innerHTML = data.sex; 19 | // document.getElementById('id').innerHTML = data.id; 20 | }); 21 | fetch('/repos/jaywcjlove/github-rank',) 22 | .then(response => response.json()) 23 | .then(data => { 24 | setGithubRank(data); 25 | }); 26 | 27 | fetch('/repos/jaywcjlove/webpack-api-mocker') 28 | .then(response => response.json()) 29 | .then(data => { 30 | setApiMocker(data); 31 | }); 32 | }, []); 33 | return ( 34 |
35 |
36 | logo 37 |

38 | Edit src/App.js and save to reload. 39 |

40 | 46 | Learn React 47 | 48 |
49 |
id: {data.id}
50 |
name: {data.username}
51 |
age: {data.sex}
52 |
53 |
URL: /repos/jaywcjlove/github-rank: {githubRank.stargazers_count}
54 |
URL: /repos/jaywcjlove/webpack-api-mocker: {apiMocker.stargazers_count}
55 |
56 |
57 |
58 | ); 59 | } 60 | 61 | export default App; 62 | -------------------------------------------------------------------------------- /example/express/mocker/index.js: -------------------------------------------------------------------------------- 1 | const delay = require('mocker-api/delay'); 2 | const user = require('./user.mock'); 3 | 4 | // 是否禁用代理 5 | const noProxy = process.env.NO_PROXY === 'true'; 6 | 7 | const proxy = { 8 | ...user, 9 | // 'GET /api/userinfo/:id': (req, res) => { 10 | // console.log('---->', req.params) 11 | // return res.json({ 12 | // id: 1, 13 | // username: 'kenny', 14 | // sex: 6 15 | // }); 16 | // }, 17 | 'GET /api/user/list/:id/:type': (req, res) => { 18 | const { type } = req.params; 19 | if (type === 'webpack') { 20 | return res.status(403).json({ 21 | status: 'error', 22 | code: 403 23 | }); 24 | } 25 | return res.json([ 26 | { 27 | id: 1, 28 | username: 'kenny', 29 | sex: 6 30 | }, { 31 | id: 2, 32 | username: 'kenny', 33 | sex: 6 34 | } 35 | ]); 36 | }, 37 | 'GET /repos/hello': (req, res) => { 38 | return res.json({ 39 | text: 'this is from mock server' 40 | }); 41 | }, 42 | 43 | 'GET /api/jobs/:id': (req, res) => { 44 | return res.json({ 45 | text: 'url: /api/jobs/:id' 46 | }); 47 | }, 48 | 49 | 'GET /api/jobs': (req, res) => { 50 | return res.json({ 51 | text: 'url: /api/jobs' 52 | }); 53 | }, 54 | 'POST /api/login/account': (req, res) => { 55 | const { password, username } = req.body; 56 | if (password === '888888' && username === 'admin') { 57 | return res.json({ 58 | status: 'ok', 59 | code: 0, 60 | token: "sdfsdfsdfdsf", 61 | data: { 62 | id: 1, 63 | username: 'kenny', 64 | sex: 6 65 | } 66 | }); 67 | } else { 68 | return res.json({ 69 | status: 'error', 70 | code: 403 71 | }); 72 | } 73 | }, 74 | 'DELETE /api/user/:id': (req, res) => { 75 | console.log('---->', req.body) 76 | console.log('---->', req.params.id) 77 | res.send({ status: 'ok', message: '删除成功!' }); 78 | }, 79 | 'GET /api/:owner/:repo/raw/:ref/*path': (req, res) => { 80 | const { owner, repo, ref, path } = req.params; 81 | // http://localhost:8081/api/admin/webpack-mock-api/raw/master/add/ddd.md 82 | // owner => admin 83 | // repo => webpack-mock-api 84 | // ref => master 85 | // req.params.path => add/ddd.md 86 | return res.json({ 87 | id: 1, 88 | owner, repo, ref, 89 | path: path 90 | }); 91 | }, 92 | } 93 | module.exports = (noProxy ? {} : delay(proxy, 1000)); 94 | // module.exports = proxy; -------------------------------------------------------------------------------- /packages/core/src/mockerHandle.ts: -------------------------------------------------------------------------------- 1 | import URL from 'url'; 2 | import bodyParser from 'body-parser'; 3 | import { Request, Response, NextFunction } from 'express'; 4 | import { MockerOption, MockerProxyRoute } from '.'; 5 | import { pathMatch } from './utils'; 6 | 7 | type MockerHandleOptions = { 8 | req: Request; 9 | res: Response; 10 | next: NextFunction; 11 | options: MockerOption; 12 | mocker: MockerProxyRoute; 13 | mockerKey: string; 14 | } 15 | 16 | export function mockerHandle(param: MockerHandleOptions) { 17 | const { options = {}, req, res, next, mocker, mockerKey } = param || {}; 18 | let bodyParserMethd = bodyParser.json({ ...options.bodyParserJSON }); // 默认使用json解析 19 | /** 20 | * `application/x-www-form-urlencoded; charset=UTF-8` => `application/x-www-form-urlencoded` 21 | * Issue: https://github.com/jaywcjlove/mocker-api/issues/50 22 | */ 23 | let contentType = req.get('Content-Type'); 24 | contentType = contentType && contentType.replace(/;.*$/, ''); 25 | if(options.bodyParserConf && options.bodyParserConf[contentType]) { 26 | // 如果存在options.bodyParserConf配置 {'text/plain': 'text','text/html': 'text'} 27 | switch(options.bodyParserConf[contentType]){// 获取bodyParser的方法 28 | case 'raw': bodyParserMethd = bodyParser.raw({...options.bodyParserRaw }); break; 29 | case 'text': bodyParserMethd = bodyParser.text({...options.bodyParserText }); break; 30 | case 'urlencoded': bodyParserMethd = bodyParser.urlencoded({extended: false, ...options.bodyParserUrlencoded }); break; 31 | case 'json': bodyParserMethd = bodyParser.json({ ...options.bodyParserJSON });//使用json解析 break; 32 | } 33 | } else { 34 | // 兼容原来的代码,默认解析 35 | // Compatible with the original code, default parsing 36 | switch(contentType){ 37 | case 'text/plain': bodyParserMethd = bodyParser.raw({...options.bodyParserRaw }); break; 38 | case 'text/html': bodyParserMethd = bodyParser.text({...options.bodyParserText }); break; 39 | case 'application/x-www-form-urlencoded': bodyParserMethd = bodyParser.urlencoded({extended: false, ...options.bodyParserUrlencoded }); break; 40 | } 41 | } 42 | 43 | bodyParserMethd(req, res, function () { 44 | const result = mocker[mockerKey]; 45 | if (typeof result === 'function') { 46 | const rgxStr = ~mockerKey.indexOf(' ') ? ' ' : ''; 47 | req.params = pathMatch({ sensitive: false, trailing: false, end: false })(mockerKey.split(new RegExp(rgxStr))[1])(URL.parse(req.url).pathname); 48 | result(req, res, next); 49 | } else { 50 | res.json(result); 51 | } 52 | }); 53 | } -------------------------------------------------------------------------------- /example/create-react-app/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/typescript/mocker/index.js: -------------------------------------------------------------------------------- 1 | const delay = require('mocker-api/delay'); 2 | const user = require('./user.mock'); 3 | 4 | // 是否禁用代理 5 | const noProxy = process.env.NO_PROXY === 'true'; 6 | 7 | const proxy = { 8 | ...user, 9 | // 'GET /api/userinfo/:id': (req, res) => { 10 | // console.log('---->', req.params) 11 | // return res.json({ 12 | // id: 1, 13 | // username: 'kenny', 14 | // sex: 6 15 | // }); 16 | // }, 17 | 'GET /api/user/list/:id/:type': (req, res) => { 18 | const { type } = req.params; 19 | console.log('req.params:', req.params); 20 | if (type === 'webpack') { 21 | return res.status(403).json({ 22 | status: 'error', 23 | code: 403 24 | }); 25 | } 26 | return res.json([ 27 | { 28 | id: 1, 29 | username: 'kenny', 30 | sex: 6 31 | }, { 32 | id: 2, 33 | username: 'kenny', 34 | sex: 6 35 | } 36 | ]); 37 | }, 38 | 'GET /api/:first': (req, res) => { 39 | console.log(req.params); // { first: 'something' } 40 | return res.json({ test: false }); 41 | }, 42 | 'GET /api/:first/items/:second': (req, res) => { 43 | console.log(req.params); // false 44 | return res.json({ test: true }); 45 | }, 46 | 'GET /api/:owner/:repo/raw/:ref/*path': (req, res) => { 47 | console.log(req.params); // false 48 | return res.json({ test: true, path: req.params.path }); 49 | }, 50 | 'GET /repos/hello': (req, res) => { 51 | return res.json({ 52 | text: 'this is from mock server' 53 | }); 54 | }, 55 | 56 | 'GET /api/jobs/:id': (req, res) => { 57 | return res.json({ 58 | text: 'url: /api/jobs/:id' 59 | }); 60 | }, 61 | 62 | 'GET /api/jobs': (req, res) => { 63 | return res.json({ 64 | text: 'url: /api/jobs' 65 | }); 66 | }, 67 | 'POST /api/login/account': (req, res) => { 68 | const { password, username } = req.body; 69 | if (password === '888888' && username === 'admin') { 70 | return res.json({ 71 | status: 'ok', 72 | code: 0, 73 | token: "sdfsdfsdfdsf", 74 | data: { 75 | id: 1, 76 | username: 'kenny', 77 | sex: 6 78 | } 79 | }); 80 | } else { 81 | return res.json({ 82 | status: 'error', 83 | code: 403 84 | }); 85 | } 86 | }, 87 | 'DELETE /api/user/:id': (req, res) => { 88 | console.log('---->', req.body) 89 | console.log('---->', req.params.id) 90 | res.send({ status: 'ok', message: '删除成功!' }); 91 | }, 92 | } 93 | module.exports = (noProxy ? {} : delay(proxy, 1000)); 94 | // module.exports = proxy; -------------------------------------------------------------------------------- /example/base/mocker/index.js: -------------------------------------------------------------------------------- 1 | const delay = require('mocker-api/delay'); 2 | const user = require('./user.mock'); 3 | 4 | // 是否禁用代理 5 | const noProxy = process.env.NO_PROXY === 'true'; 6 | 7 | const proxy = { 8 | ...user, 9 | // 'GET /api/userinfo/:id': (req, res) => { 10 | // console.log('---->', req.params) 11 | // return res.json({ 12 | // id: 1, 13 | // username: 'kenny', 14 | // sex: 6 15 | // }); 16 | // }, 17 | 'GET /api/user/list/:id/:type': (req, res) => { 18 | const { type } = req.params; 19 | console.log('req.params:', req.params); 20 | if (type === 'webpack') { 21 | return res.status(403).json({ 22 | status: 'error', 23 | code: 403 24 | }); 25 | } 26 | return res.json([ 27 | { 28 | id: 1, 29 | username: 'kenny', 30 | sex: 6 31 | }, { 32 | id: 2, 33 | username: 'kenny', 34 | sex: 6 35 | } 36 | ]); 37 | }, 38 | 'GET /api/:first': (req, res) => { 39 | console.log(req.params); // { first: 'something' } 40 | return res.json({ test: false }); 41 | }, 42 | 'GET /api/:first/items/:second': (req, res) => { 43 | console.log(req.params); // false 44 | return res.json({ test: true }); 45 | }, 46 | 'GET /api/:owner/:repo/raw/:ref/*path': (req, res) => { 47 | console.log(req.params); // false 48 | return res.json({ test: true, path: req.params.path }); 49 | }, 50 | 'GET /repos/hello': (req, res) => { 51 | return res.json({ 52 | text: 'this is from mock server' 53 | }); 54 | }, 55 | 56 | 'GET /api/jobs/:id': (req, res) => { 57 | console.log('---->', req.params) 58 | return res.json({ 59 | text: `url: /api/jobs/${req.params.id}` 60 | }); 61 | }, 62 | 63 | 'GET /api/jobs': (req, res) => { 64 | return res.json({ 65 | text: 'url: /api/jobs' 66 | }); 67 | }, 68 | 'POST /api/login/account': (req, res) => { 69 | const { password, username } = req.body; 70 | if (password === '888888' && username === 'admin') { 71 | return res.json({ 72 | status: 'ok', 73 | code: 0, 74 | token: "sdfsdfsdfdsf", 75 | data: { 76 | id: 1, 77 | username: 'kenny', 78 | sex: 6 79 | } 80 | }); 81 | } else { 82 | return res.json({ 83 | status: 'error', 84 | code: 403 85 | }); 86 | } 87 | }, 88 | 'DELETE /api/user/:id': (req, res) => { 89 | console.log('---->', req.body) 90 | console.log('---->', req.params.id) 91 | res.send({ status: 'ok', message: '删除成功!' }); 92 | }, 93 | } 94 | module.exports = (noProxy ? {} : delay(proxy, 1000)); 95 | // module.exports = proxy; -------------------------------------------------------------------------------- /example/webpack/mocker/index.js: -------------------------------------------------------------------------------- 1 | const delay = require('mocker-api/delay'); 2 | const { login } = require('./user'); 3 | 4 | // 是否禁用代理 5 | const noProxy = process.env.NO_PROXY === 'true'; 6 | 7 | const proxy = { 8 | // Priority processing. 9 | _proxy: { 10 | priority: 'mocker', 11 | proxy: { 12 | '/repos/(.*)': 'https://api.github.com/', 13 | // '/repos/jaywcjlove/webpack-api-mocker': 'https://api.github.com/repos/jaywcjlove/webpack-api-mocker', 14 | }, 15 | changeHost: true, 16 | }, 17 | 'GET /repos/jaywcjlove/webpack-api-mocker': { 18 | "stargazers_count": 11111, 19 | "subscribers_count": 6 20 | }, 21 | 'GET /repos/jaywcjlove/github-rank': { 22 | "stargazers_count": 55555555, 23 | "subscribers_count": 6 24 | }, 25 | 'GET /api/userinfo/:id': (req, res) => { 26 | console.log('-1--->', req.params) 27 | return res.json({ 28 | id: req.params.id, 29 | username: 'kenny', 30 | sex: 6 31 | }); 32 | }, 33 | 'GET /api/:owner/:repo/raw/:ref/(.*)': (req, res) => { 34 | const { owner, repo, ref } = req.params; 35 | // http://localhost:8081/api/admin/webpack-mock-api/raw/master/add/ddd.md 36 | // owner => admin 37 | // repo => webpack-mock-api 38 | // ref => master 39 | // req.params[0] => add/ddd.md 40 | return res.json({ 41 | id: 1, 42 | owner, repo, ref, 43 | path: req.params[0] 44 | }); 45 | }, 46 | 'GET /api/user/list/:id/:type': (req, res) => { 47 | const { type } = req.params; 48 | if (type === 'webpack') { 49 | return res.status(403).json({ 50 | status: 'error', 51 | code: 403 52 | }); 53 | } 54 | return res.json([ 55 | { 56 | id: 1, 57 | username: 'kenny', 58 | sex: 6 59 | }, { 60 | id: 2, 61 | username: 'kenny', 62 | sex: 6 63 | } 64 | ]); 65 | }, 66 | 'GET /repos/hello': (req, res) => { 67 | console.log('/repos/hello:=>>>', req.params) 68 | return res.json({ 69 | text: 'this is from mock server' 70 | }); 71 | }, 72 | 73 | 'GET /api/jobs/:id': (req, res) => { 74 | return res.json({ 75 | text: 'url: /api/jobs/:id' 76 | }); 77 | }, 78 | 79 | 'GET /api/jobs': (req, res) => { 80 | return res.json({ 81 | text: 'url: /api/jobs' 82 | }); 83 | }, 84 | 'POST /api/login/account': login, 85 | // 'POST /api/login/account': (req, res) => { 86 | // const { password, username } = req.body; 87 | // if (password === '888888' && username === 'admin') { 88 | // return res.json({ 89 | // status: 'ok', 90 | // code: 0, 91 | // token: "sdfsdfsdfdsf", 92 | // data: { 93 | // id: 1, 94 | // username: 'kenny', 95 | // sex: 6 96 | // } 97 | // }); 98 | // } else { 99 | // return res.json({ 100 | // status: 'error', 101 | // code: 403 102 | // }); 103 | // } 104 | // }, 105 | 'DELETE /api/user/:id': (req, res) => { 106 | console.log('--2-->', req.body) 107 | console.log('--3-->', req.params.id) 108 | res.send({ status: 'ok', message: '删除成功!' }); 109 | } 110 | } 111 | module.exports = (noProxy ? {} : delay(proxy, 1000)); 112 | // module.exports = proxy; -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | build-deploy: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | id-token: write 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-node@v4 16 | with: 17 | node-version: 20 18 | registry-url: 'https://registry.npmjs.org' 19 | 20 | - name: Look Changelog 21 | uses: jaywcjlove/changelog-generator@main 22 | with: 23 | token: ${{ secrets.GITHUB_TOKEN }} 24 | filter-author: (小弟调调™|Renovate Bot) 25 | filter: '[R|r]elease[d]\s+[v|V]\d(\.\d+){0,2}' 26 | 27 | - run: yarn install 28 | - run: yarn run build 29 | - run: yarn run coverage 30 | - run: yarn run doc 31 | 32 | - run: npm i markdown-to-html-cli -g 33 | - run: markdown-to-html -s packages/core/README.md --output packages/core/docs/index.html 34 | - run: markdown-to-html -s README-zh.md --output packages/core/docs/index.zh.html 35 | 36 | - run: npm run example:create-react-app 37 | - run: npm run example:typescript 38 | - run: npm run example:webpack 39 | 40 | - name: Generate Contributors Images 41 | uses: jaywcjlove/github-action-contributors@main 42 | with: 43 | filter-author: (renovate\[bot\]|renovate-bot|dependabot\[bot\]) 44 | output: ./packages/core/docs/CONTRIBUTORS.svg 45 | avatarSize: 43 46 | 47 | - name: Is a tag created auto? 48 | id: create_tag 49 | uses: jaywcjlove/create-tag-action@main 50 | with: 51 | package-path: ./packages/core/package.json 52 | 53 | - name: get tag version 54 | id: tag_version 55 | uses: jaywcjlove/changelog-generator@main 56 | 57 | - name: Deploy 58 | uses: peaceiris/actions-gh-pages@v4 59 | with: 60 | commit_message: ${{steps.tag_version.outputs.tag}} ${{ github.event.head_commit.message }} 61 | github_token: ${{ secrets.GITHUB_TOKEN }} 62 | publish_dir: ./packages/core/docs 63 | 64 | - name: Generate Changelog 65 | id: changelog 66 | uses: jaywcjlove/changelog-generator@main 67 | if: steps.create_tag.outputs.successful 68 | with: 69 | head-ref: ${{ steps.create_tag.outputs.version }} 70 | filter-author: (小弟调调™|Renovate Bot) 71 | filter: '[R|r]elease[d]\s+[v|V]\d(\.\d+){0,2}' 72 | 73 | - name: Create Release 74 | uses: ncipollo/release-action@v1 75 | if: steps.create_tag.outputs.successful 76 | with: 77 | allowUpdates: true 78 | token: ${{ secrets.GITHUB_TOKEN }} 79 | name: ${{ steps.create_tag.outputs.version }} 80 | tag: ${{ steps.create_tag.outputs.version }} 81 | body: | 82 | [![Buy me a coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-048754?logo=buymeacoffee)](https://jaywcjlove.github.io/#/sponsor) [![](https://img.shields.io/badge/Open%20in-unpkg-blue)](https://uiwjs.github.io/npm-unpkg/#/pkg/mocker-api@${{steps.create_tag.outputs.versionNumber}}/file/README.md) [![npm version](https://img.shields.io/npm/v/mocker-api.svg)](https://www.npmjs.com/package/mocker-api) [![npm bundle size](https://img.shields.io/bundlephobia/minzip/mocker-api)](https://bundlephobia.com/result?p=mocker-api@${{steps.create_tag.outputs.versionNumber}}) 83 | 84 | Documentation ${{ steps.changelog.outputs.tag }}: https://raw.githack.com/jaywcjlove/mocker-api/${{ steps.changelog.outputs.gh-pages-short-hash }}/index.html 85 | Comparing Changes: ${{ steps.changelog.outputs.compareurl }} 86 | 87 | ```bash 88 | npm i mocker-api@${{steps.create_tag.outputs.versionNumber}} 89 | ``` 90 | 91 | ${{ steps.changelog.outputs.changelog }} 92 | 93 | - run: npm publish --access public --provenance 94 | continue-on-error: true 95 | working-directory: packages/core 96 | env: 97 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 98 | 99 | # - uses: JS-DevTools/npm-publish@v1 100 | # with: 101 | # token: ${{ secrets.NPM_TOKEN }} 102 | # package: ./packages/core/package.json 103 | 104 | # - run: npm install @jsdevtools/npm-publish -g 105 | # - run: npm-publish --token="${{ secrets.NPM_TOKEN }}" ./packages/core/package.json 106 | -------------------------------------------------------------------------------- /packages/core/src/bin/mocker.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import path from 'path'; 3 | import { existsSync } from 'fs'; 4 | import prepareUrls from 'local-ip-url/prepareUrls'; 5 | import detect from 'detect-port'; 6 | import color from 'colors-cli/safe'; 7 | import express from 'express'; 8 | import minimist from 'minimist'; 9 | import apiMocker, { MockerOption } from '../'; 10 | 11 | interface MockerConfig extends MockerOption { 12 | host: string; 13 | port: number; 14 | } 15 | 16 | const CWD = process.cwd(); 17 | const PKG_PATH = path.resolve(CWD, './package.json'); 18 | const DEFAULT_MOCKER_CONFIG_PATH = path.resolve(CWD, './mocker.config.json'); 19 | const DEFAULT_MOCK_PATH = ['./mock']; 20 | const DEFAULT_CONFIG: MockerConfig = { 21 | host: '0.0.0.0', 22 | port: 3721 23 | }; 24 | 25 | (async () => { 26 | const argvs = minimist(process.argv.slice(2)); 27 | 28 | if (argvs.h || argvs.help) { 29 | console.log('\n Usage: mocker [--config] [--help|h]') 30 | console.log('\n Displays help information.') 31 | console.log('\n Options:') 32 | console.log(' --config ', 'Simple configuration') 33 | console.log('\n Example:') 34 | console.log(' mocker mock/index.js') 35 | console.log(' mocker mock/index.js --port 7788') 36 | console.log(' mocker mock/index.js --host 0.0.0.0') 37 | console.log(' mocker mock/m1.js test/m2.js') 38 | console.log(' mocker mock/m1.js --config mocker.config.json') 39 | console.log('\n'); 40 | return; 41 | } 42 | // Fix type errors 43 | const { version } = require('../../package.json'); 44 | 45 | if (argvs.v || argvs.version) { 46 | console.log(version); 47 | return 48 | } 49 | 50 | const paths = argvs['_']; 51 | 52 | if (paths.length === 0) { 53 | console.log(color.red('Error: Need to pass parameters!')); 54 | console.log(`E.g: ${color.yellow('mocker ')}\n`); 55 | return; 56 | } 57 | 58 | const entryFiles = paths || DEFAULT_MOCK_PATH; 59 | 60 | let mockConfigPath = argvs.config || DEFAULT_MOCKER_CONFIG_PATH; 61 | let mockerConfig = DEFAULT_CONFIG; 62 | 63 | if (argvs.config) { 64 | mockConfigPath = argvs.config; 65 | } 66 | 67 | if (!existsSync(path.resolve(mockConfigPath))) { 68 | mockerConfig.host = process.env.HOST ? process.env.HOST : mockerConfig.host; 69 | mockerConfig.port = await detect(mockerConfig.port); 70 | } else { 71 | mockerConfig = require(path.resolve(mockConfigPath)); 72 | } 73 | 74 | /** 75 | * Support setting configuration on package.json 76 | * https://github.com/jaywcjlove/mocker-api/issues/144 77 | */ 78 | if (existsSync(PKG_PATH)) { 79 | const pkgConf = require(PKG_PATH); 80 | if (pkgConf.mocker) { 81 | Object.assign(mockerConfig, pkgConf.mocker); 82 | } 83 | } 84 | 85 | if (argvs.host) { 86 | mockerConfig.host = argvs.host; 87 | } 88 | 89 | if (argvs.port) { 90 | mockerConfig.port = argvs.port; 91 | } 92 | 93 | const DEFAULT_PORT = mockerConfig.port; 94 | const DEFAULT_HOST = mockerConfig.host; 95 | const app = express(); 96 | 97 | app.all('/*', (req, res, next) => { 98 | console.log(`${color.green(req.method)} - ${req.url}`); 99 | res.header('Access-Control-Allow-Origin', '*'); 100 | res.header('Access-Control-Allow-Headers', 'Content-Type,Content-Length,Authorization,Accept,X-Requested-With'); 101 | res.header('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS'); 102 | next(); 103 | }); 104 | 105 | delete mockerConfig.port; 106 | delete mockerConfig.host; 107 | apiMocker(app, entryFiles, { ...mockerConfig }); 108 | 109 | app.listen(DEFAULT_PORT, () => { 110 | const localIpUrl = prepareUrls({ 111 | protocol: 'http', 112 | host: DEFAULT_HOST, 113 | port: DEFAULT_PORT, 114 | }); 115 | console.log(`> Server Listening at Local: ${color.green(localIpUrl.localUrl)}`); 116 | console.log(`> On Your Network: ${color.green(localIpUrl.lanUrl)}\n`); 117 | }); 118 | /** 119 | * Event listener for HTTP server "error" event. 120 | */ 121 | app.on('error', (error: any) => { 122 | if (error.syscall !== 'listen') { 123 | throw error; 124 | } 125 | const bind = typeof DEFAULT_PORT === 'string' ? `Pipe ${DEFAULT_PORT}` : `Port ${DEFAULT_PORT}`; 126 | // handle specific listen errors with friendly messages 127 | switch (error.code) { 128 | case 'EACCES': 129 | console.error(`${bind} requires elevated privileges`); // eslint-disable-line 130 | process.exit(1); 131 | break; 132 | case 'EADDRINUSE': 133 | console.error(`${bind} is already in use`); // eslint-disable-line 134 | process.exit(1); 135 | break; 136 | default: 137 | throw error; 138 | } 139 | }); 140 | })(); 141 | -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Mocker API LOGO 4 | 5 |

6 | 7 |

8 | 9 | Buy me a coffee 10 | 11 | 12 | Build & Deploy 13 | 14 | 15 | Download 16 | 17 | 18 | Repo Dependents 19 | 20 | 21 | Open in unpkg 22 | 23 | 24 | Release 25 | 26 |

27 | 28 |

29 | English · 30 | 快速开始 · 31 | 使用 · 32 | 参数设置 · 33 | 延迟响应 · 34 | 实例 · 35 | License · 36 | Type 37 |

38 | 39 | `mocker-api` 用于创建 REST API 的模拟。当你需要在没有实际 REST API 服务器的情况下测试你的应用程序时,它会非常有用。 40 | 41 | **特点:** 42 | 43 | 🔥 内置对热替换 Mocker 文件的支持。 44 | 🚀 通过 JSON 快速轻松地配置 API。 45 | 🌱 简化的 Mock API 代理。 46 | 💥 可以独立使用,不依赖于 [webpack](https://github.com/webpack/webpack) 和 [webpack-dev-server](https://github.com/webpack/webpack-dev-server)。 47 | 48 | ## 快速开始 49 | 50 | ```bash 51 | mkdir mocker-app && cd mocker-app 52 | 53 | # 根据规则创建模拟程序配置文件 54 | touch api.js 55 | 56 | # 全局安装。 57 | npm install mocker-api -g 58 | # 运行服务 59 | mocker ./api.js 60 | 61 | # Run server at localhost:8000 62 | mocker ./api.js --host localhost --port 8000 63 | ``` 64 | 65 | ## 安装 66 | 67 | 您可以将其 `package.json` 配置作为当前项目依赖项。 68 | 69 | ```bash 70 | npm install mocker-api --save-dev 71 | ``` 72 | 73 | ## 使用 74 | 75 | `mocker-api` 支持模拟,在 `mocker / index.js` 中配置。 76 | 77 | 您可以修改 [http-proxy](https://www.npmjs.com/package/http-proxy) 选项并通过添加 `httpProxy` 配置来添加事件监听器 78 | 79 | ```js 80 | const proxy = { 81 | // 优先处理。 82 | // apiMocker(app, path, option) 83 | // 这是 apiMocker 的选项参数设置 `option` 84 | _proxy: { 85 | proxy: { 86 | // 将路径字符串(例如`/user/:name`)转换为正则表达式。 87 | // https://www.npmjs.com/package/path-to-regexp 88 | '/repos/(.*)': 'https://api.github.com/', 89 | '/:owner/:repo/raw/:ref/(.*)': 'http://127.0.0.1:2018', 90 | '/api/repos/(.*)': 'http://127.0.0.1:3721/' 91 | }, 92 | // 重写目标网址路径。对象键将用作RegEx来匹配路径。 93 | // https://github.com/jaywcjlove/mocker-api/issues/62 94 | pathRewrite: { 95 | '^/api/repos/': '/repos/', 96 | }, 97 | changeHost: true, 98 | // 修改 http-proxy 选项 99 | httpProxy: { 100 | options: { 101 | ignorePath: true, 102 | }, 103 | listeners: { 104 | proxyReq: function (proxyReq, req, res, options) { 105 | console.log('proxyReq'); 106 | }, 107 | }, 108 | }, 109 | }, 110 | // ===================== 111 | // 默认的 GET 请求。 112 | // https://github.com/jaywcjlove/mocker-api/pull/63 113 | '/api/user': { 114 | id: 1, 115 | username: 'kenny', 116 | sex: 6 117 | }, 118 | 'GET /api/user': { 119 | id: 1, 120 | username: 'kenny', 121 | sex: 6 122 | }, 123 | 'GET /api/user/list': [ 124 | { 125 | id: 1, 126 | username: 'kenny', 127 | sex: 6 128 | }, { 129 | id: 2, 130 | username: 'kenny', 131 | sex: 6 132 | } 133 | ], 134 | 'GET /api/:owner/:repo/raw/:ref/(.*)': (req, res) => { 135 | const { owner, repo, ref } = req.params; 136 | // http://localhost:8081/api/admin/webpack-mock-api/raw/master/add/ddd.md 137 | // owner => admin 138 | // repo => webpack-mock-api 139 | // ref => master 140 | // req.params[0] => add/ddd.md 141 | return res.json({ 142 | id: 1, 143 | owner, repo, ref, 144 | path: req.params[0] 145 | }); 146 | }, 147 | 'POST /api/login/account': (req, res) => { 148 | const { password, username } = req.body; 149 | if (password === '888888' && username === 'admin') { 150 | return res.json({ 151 | status: 'ok', 152 | code: 0, 153 | token: "sdfsdfsdfdsf", 154 | data: { 155 | id: 1, 156 | username: 'kenny', 157 | sex: 6 158 | } 159 | }); 160 | } else { 161 | return res.status(403).json({ 162 | status: 'error', 163 | code: 403 164 | }); 165 | } 166 | }, 167 | 'DELETE /api/user/:id': (req, res) => { 168 | console.log('---->', req.body) 169 | console.log('---->', req.params.id) 170 | res.send({ status: 'ok', message: '删除成功!' }); 171 | } 172 | } 173 | module.exports = proxy; 174 | ``` 175 | 176 | ## 参数设置 177 | 178 | - [`proxy`](https://www.npmjs.com/package/path-to-regexp) => `{}` Proxy settings, Turn a path string such as `/user/:name` into a regular expression. 179 | - [`pathRewrite`](https://github.com/jaywcjlove/mocker-api/issues/62) => `{}` rewrite target's url path. Object-keys will be used as RegExp to match paths. [#62](https://github.com/jaywcjlove/mocker-api/issues/62) 180 | - `withFullUrlPath=false` => `Boolean` the proxy regular expression support full url path. if the proxy regular expression like `/test?a=1&b=1` can be matched. [#25](https://github.com/jaywcjlove/mocker-api/issues/25) 181 | - `priority` => `proxy` priority `proxy` or `mocker` [#151](https://github.com/jaywcjlove/mocker-api/issues/151) 182 | - `changeHost` => `Boolean` Setting req headers host. 183 | - `httpProxy` => `{}` Set the [listen event](https://github.com/nodejitsu/node-http-proxy#listening-for-proxy-events) and [configuration](https://github.com/nodejitsu/node-http-proxy#options) of [http-proxy](https://github.com/nodejitsu/node-http-proxy) 184 | - [`bodyParserJSON`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparserjsonoptions) JSON body parser 185 | - [`bodyParserText`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparsertextoptions) Text body parser 186 | - [`bodyParserRaw`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparserrawoptions) Raw body parser 187 | - [`bodyParserUrlencoded`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparserurlencodedoptions) URL-encoded form body parser 188 | - `bodyParserConf` => `{}` bodyParser settings. eg: `bodyParserConf : {'text/plain': 'text','text/html': 'text'}` will parsed `Content-Type='text/plain' and Content-Type='text/html'` with `bodyParser.text` 189 | - [`watchOptions`](https://github.com/paulmillr/chokidar#api) => `object` Options object as defined [chokidar api options](https://github.com/paulmillr/chokidar#api) 190 | - `header` => `{}` Access Control Allow options. 191 | ```js 192 | { 193 | header: { 194 | 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE', 195 | } 196 | } 197 | ``` 198 | 199 | ⚠️ No wildcard asterisk ~~`*`~~ - use parameters instead `(.*)`, support `v1.7.3+` 200 | 201 | ## 延迟响应 202 | 203 | 您可以使用功能性工具来增强模拟效果。[#17](https://github.com/jaywcjlove/webpack-api-mocker/issues/17) 204 | 205 | ```js 206 | const delay = require('mocker-api/delay'); 207 | const noProxy = process.env.NO_PROXY === 'true'; 208 | 209 | const proxy = { 210 | 'GET /api/user': { 211 | id: 1, 212 | username: 'kenny', 213 | sex: 6 214 | }, 215 | // ... 216 | } 217 | module.exports = (noProxy ? {} : delay(proxy, 1000)); 218 | ``` 219 | 220 | ## apiMocker 221 | 222 | ```js 223 | apiMocker(app, mockerFilePath[, options]) 224 | apiMocker(app, Mocker[, options]) 225 | ``` 226 | 227 | 多个模拟文件监听 228 | 229 | ```js 230 | const apiMocker = require('mocker-api'); 231 | const mockerFile = ['./mock/index.js']; 232 | // or 233 | // const mockerFile = './mock/index.js'; 234 | apiMocker(app, mockerFile, options) 235 | ``` 236 | 237 | ## 实例 238 | 239 | ### 在命令行中使用 240 | 241 | [Base example](example/base) 242 | 243 | >⚠️ Not dependent on [webpack](https://github.com/webpack/webpack) and [webpack-dev-server](https://github.com/webpack/webpack-dev-server). 244 | 245 | ```bash 246 | # Global install dependent. 247 | npm install mocker-api -g 248 | # Run server 249 | mocker ./mocker/index.js 250 | ``` 251 | 252 | Or you can put it the `package.json` config as a current project dependency. 253 | 254 | ```diff 255 | { 256 | "name": "base-example", 257 | "scripts": { 258 | + "api": "mocker ./mocker" 259 | }, 260 | "devDependencies": { 261 | + "mocker-api": "2.9.5" 262 | }, 263 | "license": "MIT" 264 | } 265 | ``` 266 | 267 | ### 在 [Express](https://github.com/expressjs/express) 中使用 268 | 269 | [Express example](https://github.com/jaywcjlove/mocker-api/tree/master/example/express) 270 | 271 | >⚠️ Not dependent on [webpack](https://github.com/webpack/webpack) and [webpack-dev-server](https://github.com/webpack/webpack-dev-server). 272 | 273 | ```diff 274 | const express = require('express'); 275 | + const path = require('path'); 276 | + const apiMocker = require('mocker-api'); 277 | 278 | const app = express(); 279 | 280 | + apiMocker(app, path.resolve('./mocker/index.js')) 281 | app.listen(8080); 282 | ``` 283 | 284 | or 285 | 286 | ```diff 287 | const express = require('express'); 288 | + const apiMocker = require('mocker-api'); 289 | 290 | const app = express(); 291 | 292 | + apiMocker(app, { 293 | + 'GET /api/user': { 294 | + id: 1, 295 | + sex: 0 296 | + } 297 | + }); 298 | 299 | app.listen(8080); 300 | ``` 301 | 302 | ### 在 [Webpack](https://github.com/webpack/webpack) 中使用 303 | 304 | [webpack example](https://github.com/jaywcjlove/mocker-api/tree/master/example/webpack) 305 | 306 | To use api mocker on your [Webpack](https://github.com/webpack/webpack) projects, simply add a setup options to your [webpack-dev-server](https://github.com/webpack/webpack-dev-server) options: 307 | 308 | Change your config file to tell the dev server where to look for files: `webpack.config.js`. 309 | 310 | ```diff 311 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 312 | + const path = require('path'); 313 | + const apiMocker = require('mocker-api'); 314 | 315 | module.exports = { 316 | entry: { 317 | app: './src/index.js', 318 | }, 319 | output: { 320 | filename: '[name].bundle.js', 321 | path: path.resolve(__dirname, 'dist') 322 | }, 323 | + devServer: { 324 | + ... 325 | + before(app){ 326 | + apiMocker(app, path.resolve('./mocker/index.js'), { 327 | + proxy: { 328 | + '/repos/*': 'https://api.github.com/', 329 | + '/:owner/:repo/raw/:ref/*': 'http://127.0.0.1:2018' 330 | + }, 331 | + changeHost: true, 332 | + }) 333 | + } 334 | + }, 335 | plugins: [ 336 | new HtmlWebpackPlugin({ 337 | template: path.resolve('./public/index.html'), 338 | title: 'Webpack App Mocker API' 339 | }) 340 | ], 341 | }; 342 | ``` 343 | 344 | Must have a file suffix! For example: `./mocker/index.js`. 345 | 346 | Let's add a script to easily run the dev server as well: `package.json` 347 | 348 | ```diff 349 | { 350 | "name": "development", 351 | "version": "1.0.0", 352 | "description": "", 353 | "main": "webpack.config.js", 354 | "scripts": { 355 | "test": "echo \"Error: no test specified\" && exit 1", 356 | + "start": "webpack serve --progress --mode development", 357 | "build": "webpack --mode production" 358 | }, 359 | "keywords": [], 360 | "author": "", 361 | "license": "MIT", 362 | "devDependencies": { 363 | "html-webpack-plugin": "4.5.0", 364 | "mocker-api": "2.9.5", 365 | "webpack": "5.22.0", 366 | "webpack-cli": "4.5.0", 367 | "webpack-dev-server": "3.11.2" 368 | } 369 | } 370 | ``` 371 | 372 | Mock API proxying made simple. 373 | 374 | ```diff 375 | { 376 | before(app){ 377 | + apiMocker(app, path.resolve('./mocker/index.js'), { 378 | + proxy: { 379 | + '/repos/*': 'https://api.github.com/', 380 | + }, 381 | + changeHost: true, 382 | + }) 383 | } 384 | } 385 | ``` 386 | 387 | ### 在 create-react-app 中使用 388 | 389 | [create-react-app example](https://github.com/jaywcjlove/mocker-api/tree/master/example/create-react-app) 390 | 391 | 创建 [`src/setupProxy.js`](https://github.com/jaywcjlove/mocker-api/blob/64a093685b05c70ab0ddcf3fd5dbede7871efa8a/example/create-react-app/src/setupProxy.js#L1-L11) 并放置以下内容: 392 | 393 | ```diff 394 | + const apiMocker = require('mocker-api'); 395 | + const path = require('path'); 396 | 397 | module.exports = function(app) { 398 | + apiMocker(app, path.resolve('./mocker/index.js'), { 399 | + proxy: { 400 | + '/repos/(.*)': 'https://api.github.com/', 401 | + }, 402 | + changeHost: true, 403 | + }); 404 | }; 405 | ``` 406 | 407 | ```diff 408 | { 409 | ..... 410 | "devDependencies": { 411 | + "mocker-api": "2.9.5" 412 | }, 413 | .... 414 | } 415 | ``` 416 | 417 | 418 | ### 开发 419 | 420 | ```shell 421 | $ yarn install 422 | $ yarn run build 423 | $ yarn run watch 424 | $ yarn run test 425 | ``` 426 | 427 | ## Contributors 428 | 429 | As always, thanks to our amazing contributors! 430 | 431 | 432 | 433 | 434 | 435 | Made with [github-action-contributors](https://github.com/jaywcjlove/github-action-contributors). 436 | 437 | ## License 438 | 439 | [MIT © Kenny Wong](https://github.com/jaywcjlove/mocker-api/blob/master/LICENSE) 440 | -------------------------------------------------------------------------------- /packages/core/src/index.ts: -------------------------------------------------------------------------------- 1 | import URL from 'url'; 2 | import PATH from 'path'; 3 | import * as net from "net"; 4 | import * as http from "http"; 5 | import { Request, Response, NextFunction, Application } from 'express'; 6 | import bodyParser from 'body-parser'; 7 | import httpProxy from 'http-proxy'; 8 | import * as toRegexp from 'path-to-regexp'; 9 | import clearModule from 'clear-module'; 10 | import chokidar, { ChokidarOptions } from 'chokidar'; 11 | import color from 'colors-cli/safe'; 12 | import { proxyHandle } from './proxyHandle'; 13 | import { mockerHandle } from './mockerHandle'; 14 | 15 | export * from './delay'; 16 | export * from './utils'; 17 | 18 | export type ProxyTargetUrl = string | Partial; 19 | export type MockerResultFunction = ((req: Request, res: Response, next?: NextFunction) => void); 20 | export type MockerResult = string | number| Array | Record | MockerResultFunction; 21 | 22 | /** 23 | * Setting a proxy router. 24 | * @example 25 | * 26 | * ```json 27 | * { 28 | * '/api/user': { 29 | * id: 1, 30 | * username: 'kenny', 31 | * sex: 6 32 | * }, 33 | * 'DELETE /api/user/:id': (req, res) => { 34 | * res.send({ status: 'ok', message: '删除成功!' }); 35 | * } 36 | * } 37 | * ``` 38 | */ 39 | export type MockerProxyRoute = Record & { 40 | /** 41 | * This is the option parameter setting for apiMocker 42 | * Priority processing. 43 | * apiMocker(app, path, option) 44 | * {@link MockerOption} 45 | */ 46 | _proxy?: MockerOption; 47 | } 48 | 49 | /** 50 | * Listening for proxy events. 51 | * This options contains listeners for [node-http-proxy](https://github.com/http-party/node-http-proxy#listening-for-proxy-events). 52 | * {typeof httpProxy.on} 53 | * {@link httpProxy} 54 | */ 55 | export interface HttpProxyListeners extends Record { 56 | start?: ( 57 | req: http.IncomingMessage, 58 | res: http.ServerResponse, 59 | target: ProxyTargetUrl 60 | ) => void; 61 | proxyReq?: ( 62 | proxyReq: http.ClientRequest, 63 | req: http.IncomingMessage, 64 | res: http.ServerResponse, 65 | options: httpProxy.ServerOptions 66 | ) => void; 67 | proxyRes?: ( 68 | proxyRes: http.IncomingMessage, 69 | req: http.IncomingMessage, 70 | res: http.ServerResponse 71 | ) => void; 72 | proxyReqWs?: ( 73 | proxyReq: http.ClientRequest, 74 | req: http.IncomingMessage, 75 | socket: net.Socket, 76 | options: httpProxy.ServerOptions, 77 | head: any 78 | ) => void; 79 | econnreset?: ( 80 | err: Error, 81 | req: http.IncomingMessage, 82 | res: http.ServerResponse, 83 | target: ProxyTargetUrl 84 | ) => void 85 | end?: ( 86 | req: http.IncomingMessage, 87 | res: http.ServerResponse, 88 | proxyRes: http.IncomingMessage 89 | ) => void; 90 | /** 91 | * This event is emitted once the proxy websocket was closed. 92 | */ 93 | close?: ( 94 | proxyRes: http.IncomingMessage, 95 | proxySocket: net.Socket, 96 | proxyHead: any 97 | ) => void; 98 | } 99 | 100 | export interface MockerOption { 101 | /** 102 | * priority 'proxy' or 'mocker' 103 | * @default `proxy` 104 | * @issue [#151](https://github.com/jaywcjlove/mocker-api/issues/151) 105 | */ 106 | priority?: 'proxy' | 'mocker'; 107 | /** 108 | * `Boolean` Setting req headers host. 109 | */ 110 | changeHost?: boolean; 111 | /** 112 | * rewrite target's url path. 113 | * Object-keys will be used as RegExp to match paths. [#62](https://github.com/jaywcjlove/mocker-api/issues/62) 114 | * @default `{}` 115 | */ 116 | pathRewrite?: Record, 117 | /** 118 | * Proxy settings, Turn a path string such as `/user/:name` into a regular expression. [path-to-regexp](https://www.npmjs.com/package/path-to-regexp) 119 | * @default `{}` 120 | */ 121 | proxy?: Record, 122 | /** 123 | * Set the [listen event](https://github.com/nodejitsu/node-http-proxy#listening-for-proxy-events) and [configuration](https://github.com/nodejitsu/node-http-proxy#options) of [http-proxy](https://github.com/nodejitsu/node-http-proxy) 124 | * @default `{}` 125 | */ 126 | httpProxy?: { 127 | options?: httpProxy.ServerOptions; 128 | listeners?: HttpProxyListeners 129 | }; 130 | /** 131 | * bodyParser settings. 132 | * @example 133 | * 134 | * ```js 135 | * bodyParser = {"text/plain": "text","text/html": "text"} 136 | * ``` 137 | * 138 | * will parsed `Content-Type='text/plain' and Content-Type='text/html'` with `bodyParser.text` 139 | * 140 | * @default `{}` 141 | */ 142 | bodyParserConf?: { 143 | [key: string]: 'raw' | 'text' | 'urlencoded' | 'json'; 144 | }; 145 | /** 146 | * [`bodyParserJSON`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparserjsonoptions) JSON body parser 147 | * @default `{}` 148 | */ 149 | bodyParserJSON?: bodyParser.OptionsJson; 150 | /** 151 | * [`bodyParserText`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparsertextoptions) Text body parser 152 | * @default `{}` 153 | */ 154 | bodyParserText?: bodyParser.OptionsText; 155 | /** 156 | * [`bodyParserRaw`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparserrawoptions) Raw body parser 157 | * @default `{}` 158 | */ 159 | bodyParserRaw?: bodyParser.Options; 160 | /** 161 | * [`bodyParserUrlencoded`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparserurlencodedoptions) URL-encoded form body parser 162 | * @default `{}` 163 | */ 164 | bodyParserUrlencoded?: bodyParser.OptionsUrlencoded; 165 | /** 166 | * Options object as defined [chokidar api options](https://github.com/paulmillr/chokidar#api) 167 | * @default `{}` 168 | */ 169 | watchOptions?: ChokidarOptions; 170 | /** 171 | * Access Control Allow options. 172 | * @default `{}` 173 | * @example 174 | * ```js 175 | * { 176 | * header: { 177 | * 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE', 178 | * } 179 | * } 180 | * ``` 181 | */ 182 | header?: Record, 183 | /** 184 | * `Boolean` the proxy regular expression support full url path. 185 | * if the proxy regular expression like /test?a=1&b=1 can be matched 186 | */ 187 | withFullUrlPath?: boolean 188 | } 189 | 190 | const pathToRegexp = toRegexp.pathToRegexp; 191 | let mocker: MockerProxyRoute = {}; 192 | 193 | module.exports = mockerApi 194 | 195 | export default function mockerApi(app: Application, watchFile: string | string[] | MockerProxyRoute, conf: MockerOption = {}) { 196 | const watchFiles = (Array.isArray(watchFile) ? watchFile : typeof watchFile === 'string' ? [watchFile] : []).map(str => PATH.resolve(str)); 197 | 198 | if (watchFiles.some(file => !file)) { 199 | throw new Error('Mocker file does not exist!.'); 200 | } 201 | 202 | /** 203 | * Mybe watch file or pass parameters 204 | * https://github.com/jaywcjlove/mocker-api/issues/116 205 | */ 206 | const isWatchFilePath = (Array.isArray(watchFile) && watchFile.every(val => typeof val === 'string')) || typeof watchFile === 'string'; 207 | mocker = isWatchFilePath ? getConfig() : watchFile; 208 | 209 | if (!mocker) { 210 | return (req: Request, res: Response, next: NextFunction) => { 211 | next(); 212 | } 213 | } 214 | let options: MockerOption = {...conf, ...(mocker._proxy || {})} 215 | const defaultOptions: MockerOption = { 216 | changeHost: true, 217 | pathRewrite: {}, 218 | proxy: {}, 219 | // proxy: proxyConf: {}, 220 | httpProxy: {}, 221 | // httpProxy: httpProxyConf: {}, 222 | bodyParserConf: {}, 223 | bodyParserJSON: {}, 224 | bodyParserText: {}, 225 | bodyParserRaw: {}, 226 | bodyParserUrlencoded: {}, 227 | watchOptions: {}, 228 | header: {}, 229 | priority: 'proxy', 230 | withFullUrlPath: false 231 | } 232 | 233 | options = { ...defaultOptions, ...options }; 234 | // changeHost = true, 235 | // pathRewrite = {}, 236 | // proxy: proxyConf = {}, 237 | // httpProxy: httpProxyConf = {}, 238 | // bodyParserConf= {}, 239 | // bodyParserJSON = {}, 240 | // bodyParserText = {}, 241 | // bodyParserRaw = {}, 242 | // bodyParserUrlencoded = {}, 243 | // watchOptions = {}, 244 | // header = {} 245 | 246 | if (isWatchFilePath) { 247 | // 监听配置入口文件所在的目录,一般为认为在配置文件/mock 目录下的所有文件 248 | // 加上require.resolve,保证 `./mock/`能够找到`./mock/index.js`,要不然就要监控到上一级目录了 249 | const watcher = chokidar.watch(watchFiles.map(watchFile => PATH.dirname(require.resolve(watchFile))), options.watchOptions); 250 | 251 | watcher.on('all', (event, path) => { 252 | if (event === 'change' || event === 'add') { 253 | try { 254 | // 当监听的可能是多个配置文件时,需要清理掉更新文件以及入口文件的缓存,重新获取 255 | cleanCache(path); 256 | watchFiles.forEach(file => cleanCache(file)); 257 | mocker = getConfig(); 258 | if (mocker._proxy) { 259 | options = { ...options, ...mocker._proxy }; 260 | } 261 | console.log(`${color.green_b.black(' Done: ')} Hot Mocker ${color.green(path.replace(process.cwd(), ''))} file replacement success!`); 262 | } catch (ex) { 263 | console.error(`${color.red_b.black(' Failed: ')} Hot Mocker ${color.red(path.replace(process.cwd(), ''))} file replacement failed!!`); 264 | } 265 | } 266 | }) 267 | } 268 | // 监听文件修改重新加载代码 269 | // 配置热更新 270 | app.all('/*', (req: Request, res: Response, next: NextFunction) => { 271 | const getExecUrlPath = (req: Request) => { 272 | return options.withFullUrlPath ? req.url : req.path; 273 | } 274 | /** 275 | * Get Proxy key 276 | */ 277 | const proxyKey = Object.keys(options.proxy).find((kname) => { 278 | try { 279 | const { regexp } = pathToRegexp(kname.replace((new RegExp('^' + req.method + ' ')), '')) 280 | return !!regexp.exec(getExecUrlPath(req)); 281 | } catch (error) { 282 | console.error(`${color.red_b.black(' Failed: ')} The proxy configuration ${color.red(kname)} contains a syntax error!!\n doc: ${color.blue("https://www.npmjs.com/package/path-to-regexp/v/8.2.0")}`); 283 | return false; 284 | } 285 | }); 286 | /** 287 | * Get Mocker key 288 | * => `GET /api/:owner/:repo/raw/:ref` 289 | * => `GET /api/:owner/:repo/raw/:ref/(.*)` 290 | */ 291 | const mockerKey: string = Object.keys(mocker).find((kname) => { 292 | try { 293 | const { regexp } = pathToRegexp(kname.replace((new RegExp('^' + req.method + ' ')), '')) 294 | return !!regexp.exec(getExecUrlPath(req)); 295 | } catch (error) { 296 | console.error(`${color.red_b.black(' Failed: ')} The mocker configuration ${color.red(kname)} contains a syntax error!!\n doc: ${color.blue("https://www.npmjs.com/package/path-to-regexp/v/8.2.0")}`); 297 | return false; 298 | } 299 | }); 300 | /** 301 | * Access Control Allow options. 302 | * https://github.com/jaywcjlove/mocker-api/issues/61 303 | */ 304 | const accessOptions: MockerOption['header'] = { 305 | 'Access-Control-Allow-Origin': req.get('Origin') || '*', 306 | 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE', 307 | 'Access-Control-Allow-Headers': 'Content-Type, X-Requested-With,' + (req.header('access-control-request-headers') || ''), 308 | 'Access-Control-Allow-Credentials': 'true', 309 | ...options.header, 310 | } 311 | Object.keys(accessOptions).forEach(keyName => { 312 | res.setHeader(keyName, accessOptions[keyName]); 313 | }); 314 | const proxyKeyString: string = Object.keys(mocker).find((kname) => { 315 | try { 316 | const { regexp } = pathToRegexp(kname.replace((new RegExp('^(PUT|POST|GET|DELETE) ')), '')) 317 | return !!regexp.exec(getExecUrlPath(req)) 318 | } catch (error) { 319 | console.error(`${color.red_b.black(' Failed: ')} The mocker configuration ${color.red(kname)} contains a syntax error!!\n doc: ${color.blue("https://www.npmjs.com/package/path-to-regexp/v/8.2.0")}`); 320 | return false; 321 | } 322 | }) 323 | // fix issue 34 https://github.com/jaywcjlove/mocker-api/issues/34 324 | // In some cross-origin http request, the browser will send the preflighted options request before sending the request methods written in the code. 325 | if (!mockerKey && req.method.toLocaleUpperCase() === 'OPTIONS' && proxyKeyString) { 326 | return res.sendStatus(200); 327 | } 328 | 329 | /** 330 | * priority 'proxy' or 'mocker' [#151](https://github.com/jaywcjlove/mocker-api/issues/151) 331 | */ 332 | if (options.priority === 'mocker') { 333 | if (mocker[mockerKey]) { 334 | return mockerHandle({ req, res, next, mocker, options, mockerKey}) 335 | } else if (proxyKey && options.proxy[proxyKey]) { 336 | return proxyHandle(req, res, options, proxyKey); 337 | } 338 | } else { 339 | if (proxyKey && options.proxy[proxyKey]) { 340 | return proxyHandle(req, res, options, proxyKey); 341 | } else if (mocker[mockerKey]) { 342 | return mockerHandle({ req, res, next, mocker, options, mockerKey}) 343 | } 344 | } 345 | 346 | next(); 347 | }); 348 | 349 | /** 350 | * The old module's resources to be released. 351 | * @param modulePath 352 | */ 353 | function cleanCache(modulePath: string) { 354 | // The entry file does not have a .js suffix, 355 | // causing the module's resources not to be released. 356 | // https://github.com/jaywcjlove/webpack-api-mocker/issues/30 357 | try { 358 | modulePath = require.resolve(modulePath); 359 | } catch (e) {} 360 | var module = require.cache[modulePath]; 361 | if (!module) return; 362 | // https://github.com/jaywcjlove/mocker-api/issues/42 363 | clearModule(modulePath); 364 | } 365 | /** 366 | * Merge multiple Mockers 367 | */ 368 | function getConfig() { 369 | return watchFiles.reduce((mocker, file) => { 370 | const mockerItem = require(file); 371 | return Object.assign(mocker, mockerItem.default ? mockerItem.default : mockerItem); 372 | }, {}) 373 | } 374 | return (req: Request, res: Response, next: NextFunction) => { 375 | next(); 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Mocker API LOGO 4 | 5 |

6 | 7 | 8 |

9 | 10 | Buy me a coffee 11 | 12 | 13 | Build & Deploy 14 | 15 | 16 | Download 17 | 18 | 19 | Repo Dependents 20 | 21 | 22 | Open in unpkg 23 | 24 | 25 | Release 26 | 27 | 28 | npm version 29 | 30 |

31 | 32 |

33 | 中文 · 34 | Quick Start · 35 | Usage · 36 | Options · 37 | Delayed · 38 | Example · 39 | License · 40 | type 41 |

42 | 43 | `mocker-api` creates mocks for REST APIs. It is helpful when you need to test your application without the actual REST API server. 44 | 45 | **Features:** 46 | 47 | 🔥 Built-in support for hot Mocker file replacement. 48 | 🚀 Quickly and easily configure the API via JSON. 49 | 🌱 Mock API proxying made simple. 50 | 💥 Can be used independently without relying on [webpack](https://github.com/webpack/webpack) and [webpack-dev-server](https://github.com/webpack/webpack-dev-server). 51 | 52 | ## Quick Start 53 | 54 | ```bash 55 | mkdir mocker-app && cd mocker-app 56 | 57 | # Create a mocker configuration file based on rules 58 | touch api.js 59 | 60 | # Global install dependent. 61 | npm install mocker-api -g 62 | 63 | # Default port: 3721 64 | mocker ./api.js 65 | 66 | # Designated port 67 | # Run server at localhost:8000 68 | mocker ./api.js --host localhost --port 8000 69 | ``` 70 | 71 | ## Installation 72 | 73 | you can put it the `package.json` config as a current project dependency. 74 | 75 | ```bash 76 | npm install mocker-api --save-dev 77 | ``` 78 | 79 | ## Usage 80 | 81 | `mocker-api` dev support mock, configured in `mocker/index.js`. 82 | 83 | you can modify the [http-proxy](https://www.npmjs.com/package/http-proxy) options and add the event listeners by adding the httpProxy configuration 84 | 85 | ```js 86 | const proxy = { 87 | // Priority processing. 88 | // apiMocker(app, path, option) 89 | // This is the option parameter setting for apiMocker 90 | _proxy: { 91 | proxy: { 92 | // Turn a path string such as `/user/:name` into a regular expression. 93 | // https://www.npmjs.com/package/path-to-regexp 94 | '/repos/*path': 'https://api.github.com/', 95 | '/:owner/:repo/raw/:ref/*path': 'http://127.0.0.1:2018', 96 | '/api/repos/*path': 'http://127.0.0.1:3721/' 97 | }, 98 | // rewrite target's url path. Object-keys will be used as RegExp to match paths. 99 | // https://github.com/jaywcjlove/mocker-api/issues/62 100 | pathRewrite: { 101 | '^/api/repos/': '/repos/', 102 | }, 103 | changeHost: true, 104 | // modify the http-proxy options 105 | httpProxy: { 106 | options: { 107 | ignorePath: true, 108 | }, 109 | listeners: { 110 | proxyReq: function (proxyReq, req, res, options) { 111 | console.log('proxyReq'); 112 | }, 113 | }, 114 | }, 115 | }, 116 | // ===================== 117 | // The default GET request. 118 | // https://github.com/jaywcjlove/mocker-api/pull/63 119 | '/api/user': { 120 | id: 1, 121 | username: 'kenny', 122 | sex: 6 123 | }, 124 | 'GET /api/user': { 125 | id: 1, 126 | username: 'kenny', 127 | sex: 6 128 | }, 129 | 'GET /api/user/list': [ 130 | { 131 | id: 1, 132 | username: 'kenny', 133 | sex: 6 134 | }, { 135 | id: 2, 136 | username: 'kenny', 137 | sex: 6 138 | } 139 | ], 140 | 'GET /api/:owner/:repo/raw/:ref/*path': (req, res) => { 141 | const { owner, repo, ref } = req.params; 142 | // http://localhost:8081/api/admin/webpack-mock-api/raw/master/add/ddd.md 143 | // owner => admin 144 | // repo => webpack-mock-api 145 | // ref => master 146 | // req.params.path => add/ddd.md 147 | return res.json({ 148 | id: 1, 149 | owner, repo, ref, 150 | path: req.params.path 151 | }); 152 | }, 153 | 'POST /api/login/account': (req, res) => { 154 | const { password, username } = req.body; 155 | if (password === '888888' && username === 'admin') { 156 | return res.json({ 157 | status: 'ok', 158 | code: 0, 159 | token: "sdfsdfsdfdsf", 160 | data: { 161 | id: 1, 162 | username: 'kenny', 163 | sex: 6 164 | } 165 | }); 166 | } else { 167 | return res.status(403).json({ 168 | status: 'error', 169 | code: 403 170 | }); 171 | } 172 | }, 173 | 'DELETE /api/user/:id': (req, res) => { 174 | console.log('---->', req.body) 175 | console.log('---->', req.params.id) 176 | res.send({ status: 'ok', message: '删除成功!' }); 177 | } 178 | } 179 | module.exports = proxy; 180 | ``` 181 | 182 | ## Options 183 | 184 | - [`proxy`](https://www.npmjs.com/package/path-to-regexp) => `{}` Proxy settings, Turn a path string such as `/user/:name` into a regular expression. 185 | - [`pathRewrite`](https://github.com/jaywcjlove/mocker-api/issues/62) => `{}` rewrite target's url path. Object-keys will be used as RegExp to match paths. [#62](https://github.com/jaywcjlove/mocker-api/issues/62) 186 | - `withFullUrlPath=false` => `Boolean` the proxy regular expression support full url path. if the proxy regular expression like `/test?a=1&b=1` can be matched. [#25](https://github.com/jaywcjlove/mocker-api/issues/25) 187 | - `priority` => `proxy` priority `proxy` or `mocker` [#151](https://github.com/jaywcjlove/mocker-api/issues/151) 188 | - `changeHost` => `Boolean` Setting req headers host. 189 | - `httpProxy` => `{}` Set the [listen event](https://github.com/nodejitsu/node-http-proxy#listening-for-proxy-events) and [configuration](https://github.com/nodejitsu/node-http-proxy#options) of [http-proxy](https://github.com/nodejitsu/node-http-proxy) 190 | - [`bodyParserJSON`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparserjsonoptions) JSON body parser 191 | - [`bodyParserText`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparsertextoptions) Text body parser 192 | - [`bodyParserRaw`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparserrawoptions) Raw body parser 193 | - [`bodyParserUrlencoded`](https://github.com/expressjs/body-parser/tree/56a2b73c26b2238bc3050ad90af9ab9c62f4eb97#bodyparserurlencodedoptions) URL-encoded form body parser 194 | - `bodyParserConf` => `{}` bodyParser settings. eg: `bodyParserConf : {'text/plain': 'text','text/html': 'text'}` will parsed `Content-Type='text/plain' and Content-Type='text/html'` with `bodyParser.text` 195 | - [`watchOptions`](https://github.com/paulmillr/chokidar#api) => `object` Options object as defined [chokidar api options](https://github.com/paulmillr/chokidar#api) 196 | - `header` => `{}` Access Control Allow options. 197 | ```js 198 | { 199 | header: { 200 | 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE', 201 | } 202 | } 203 | ``` 204 | 205 | ⚠️ No wildcard asterisk ~~`*`~~ - use parameters instead `(.*)`, support `v1.7.3+` 206 | 207 | ⚠️ No wildcard asterisk ~~`(.*)`~~ - use parameters instead `*path`, support `v3.0.0+` 208 | 209 | ## Delayed Response 210 | 211 | You can use functional tool to enhance mock. [#17](https://github.com/jaywcjlove/webpack-api-mocker/issues/17) 212 | 213 | ```js 214 | const delay = require('mocker-api/delay'); 215 | const noProxy = process.env.NO_PROXY === 'true'; 216 | 217 | const proxy = { 218 | 'GET /api/user': { 219 | id: 1, 220 | username: 'kenny', 221 | sex: 6 222 | }, 223 | // ... 224 | } 225 | module.exports = (noProxy ? {} : delay(proxy, 1000)); 226 | ``` 227 | 228 | ## apiMocker 229 | 230 | ```js 231 | apiMocker(app, mockerFilePath[, options]) 232 | apiMocker(app, Mocker[, options]) 233 | ``` 234 | 235 | Multi entry `mocker` file watching 236 | 237 | ```js 238 | const apiMocker = require('mocker-api'); 239 | const mockerFile = ['./mock/index.js']; 240 | // or 241 | // const mockerFile = './mock/index.js'; 242 | apiMocker(app, mockerFile, options) 243 | ``` 244 | 245 | ## Example 246 | 247 | ### Using With Command 248 | 249 | [Base example](example/base) 250 | 251 | >⚠️ Not dependent on [webpack](https://github.com/webpack/webpack) and [webpack-dev-server](https://github.com/webpack/webpack-dev-server). 252 | 253 | ```bash 254 | # Global install dependent. 255 | npm install mocker-api -g 256 | # Run server 257 | mocker ./mocker/index.js 258 | ``` 259 | 260 | Or you can put it the `package.json` config as a current project dependency. 261 | 262 | ```diff 263 | { 264 | "name": "base-example", 265 | "scripts": { 266 | + "api": "mocker ./mocker" 267 | }, 268 | "devDependencies": { 269 | + "mocker-api": "2.9.5" 270 | }, 271 | + "mocker": { 272 | + "port": 7788 273 | + }, 274 | "license": "MIT" 275 | } 276 | ``` 277 | 278 | ### Using With [Express](https://github.com/expressjs/express) 279 | 280 | [Express example](https://github.com/jaywcjlove/mocker-api/tree/master/example/express) 281 | 282 | To use api mocker on your [express](https://github.com/expressjs/express) projects. 283 | 284 | >⚠️ Not dependent on [webpack](https://github.com/webpack/webpack) and [webpack-dev-server](https://github.com/webpack/webpack-dev-server). 285 | 286 | ```diff 287 | const express = require('express'); 288 | + const path = require('path'); 289 | + const apiMocker = require('mocker-api'); 290 | 291 | const app = express(); 292 | 293 | + apiMocker(app, path.resolve('./mocker/index.js')) 294 | app.listen(8080); 295 | ``` 296 | 297 | or 298 | 299 | ```diff 300 | const express = require('express'); 301 | + const apiMocker = require('mocker-api'); 302 | 303 | const app = express(); 304 | 305 | + apiMocker(app, { 306 | + 'GET /api/user': { 307 | + id: 1, 308 | + sex: 0 309 | + } 310 | + }); 311 | 312 | app.listen(8080); 313 | ``` 314 | 315 | ### Using With [Webpack](https://github.com/webpack/webpack) 316 | 317 | [webpack example](https://github.com/jaywcjlove/mocker-api/tree/master/example/webpack) 318 | 319 | To use api mocker on your [Webpack](https://github.com/webpack/webpack) projects, simply add a setup options to your [webpack-dev-server](https://github.com/webpack/webpack-dev-server) options: 320 | 321 | Change your config file to tell the dev server where to look for files: `webpack.config.js`. 322 | 323 | ```diff 324 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 325 | + const path = require('path'); 326 | + const apiMocker = require('mocker-api'); 327 | 328 | module.exports = { 329 | entry: { 330 | app: './src/index.js', 331 | }, 332 | output: { 333 | filename: '[name].bundle.js', 334 | path: path.resolve(__dirname, 'dist') 335 | }, 336 | + devServer: { 337 | + ... 338 | + before(app){ 339 | + apiMocker(app, path.resolve('./mocker/index.js'), { 340 | + proxy: { 341 | + '/repos/*path': 'https://api.github.com/', 342 | + '/:owner/:repo/raw/:ref/*path': 'http://127.0.0.1:2018' 343 | + }, 344 | + changeHost: true, 345 | + }) 346 | + } 347 | + }, 348 | plugins: [ 349 | new HtmlWebpackPlugin({ 350 | template: path.resolve('./public/index.html'), 351 | title: 'Webpack App Mocker API' 352 | }) 353 | ], 354 | }; 355 | ``` 356 | 357 | Must have a file suffix! For example: `./mocker/index.js`. 358 | 359 | Let's add a script to easily run the dev server as well: `package.json` 360 | 361 | ```diff 362 | { 363 | "name": "development", 364 | "version": "1.0.0", 365 | "description": "", 366 | "main": "webpack.config.js", 367 | "scripts": { 368 | "test": "echo \"Error: no test specified\" && exit 1", 369 | + "start": "webpack serve --progress --mode development", 370 | "build": "webpack --mode production" 371 | }, 372 | "keywords": [], 373 | "author": "", 374 | "license": "MIT", 375 | "devDependencies": { 376 | "html-webpack-plugin": "4.5.0", 377 | "mocker-api": "2.9.5", 378 | "webpack": "5.22.0", 379 | "webpack-cli": "4.5.0", 380 | "webpack-dev-server": "3.11.2" 381 | } 382 | } 383 | ``` 384 | 385 | Mock API proxying made simple. 386 | 387 | ```diff 388 | { 389 | before(app){ 390 | + apiMocker(app, path.resolve('./mocker/index.js'), { 391 | + proxy: { 392 | + '/repos/*path': 'https://api.github.com/', 393 | + }, 394 | + changeHost: true, 395 | + }) 396 | } 397 | } 398 | ``` 399 | 400 | ### Using With create-react-app 401 | 402 | [create-react-app example](https://github.com/jaywcjlove/mocker-api/tree/master/example/create-react-app) 403 | 404 | To use api mocker on your [create-react-app](https://github.com/facebook/create-react-app/blob/3f699fd08044de9ab0ce1991a66b376d3e1956a8/docusaurus/docs/proxying-api-requests-in-development.md) projects. create [`src/setupProxy.js`](https://github.com/jaywcjlove/mocker-api/blob/64a093685b05c70ab0ddcf3fd5dbede7871efa8a/example/create-react-app/src/setupProxy.js#L1-L11) and place the following contents in it: 405 | 406 | ```diff 407 | + const apiMocker = require('mocker-api'); 408 | + const path = require('path'); 409 | 410 | module.exports = function(app) { 411 | + apiMocker(app, path.resolve('./mocker/index.js'), { 412 | + proxy: { 413 | + '/repos/*path': 'https://api.github.com/', 414 | + }, 415 | + changeHost: true, 416 | + }); 417 | }; 418 | ``` 419 | 420 | ```diff 421 | { 422 | ..... 423 | "devDependencies": { 424 | + "mocker-api": "2.9.5" 425 | }, 426 | .... 427 | } 428 | ``` 429 | 430 | ### Development 431 | 432 | ```shell 433 | $ yarn install 434 | $ yarn run build 435 | $ yarn run watch 436 | $ yarn run test 437 | ``` 438 | 439 | ## Contributors 440 | 441 | As always, thanks to our amazing contributors! 442 | 443 | 444 | 445 | 446 | 447 | Made with [github-action-contributors](https://github.com/jaywcjlove/github-action-contributors). 448 | 449 | ## License 450 | 451 | [MIT © Kenny Wong](https://github.com/jaywcjlove/mocker-api/blob/master/LICENSE) 452 | --------------------------------------------------------------------------------