1 | const path = require("path");
|
2 | const load = require("../../util/load");
|
3 | const webpack = load("webpack");
|
4 | const cwd = process.cwd();
|
5 | const axios = require("axios");
|
6 | const getTimeFromDate = date => date.toTimeString().slice(0, 8);
|
7 |
|
8 | const {
|
9 | YNW_CONFIG_PATH,
|
10 | PRODUCTION,
|
11 | DEVELOPMENT
|
12 | } = require("../../util/const");
|
13 | const { getPageOption } = require("../../util/fns");
|
14 | const openBrowser = require("../../util/openBrowser");
|
15 |
|
16 | module.exports = argv => main(argv);
|
17 |
|
18 | async function main(argv) {
|
19 | const package = require("../../package.json");
|
20 | const optionDecorator = require("./options/optionDecorator");
|
21 |
|
22 | console.log(`> ynw-cli: ${package.version}`.cyan);
|
23 |
|
24 | const inputs = await parseInput(argv);
|
25 | beforeOption(inputs);
|
26 | const options = optionDecorator(createWebpackOption(inputs));
|
27 | beforeCompiler(inputs, options);
|
28 | run(inputs, options);
|
29 | }
|
30 |
|
31 | async function parseInput(argv) {
|
32 | const { build, env } = argv;
|
33 | const defaultOption = {
|
34 | target: "web",
|
35 | env: "dev",
|
36 | port: 9999
|
37 | };
|
38 | const config = require(YNW_CONFIG_PATH);
|
39 | const pageOption = getPageOption(config, build);
|
40 |
|
41 |
|
42 | const options = Object.assign(defaultOption, config.common, pageOption, argv);
|
43 | const isHot = env === "hot";
|
44 | const isDev = env === "dev";
|
45 | const isPro = env === "pro";
|
46 | const notPro = isDev || isHot;
|
47 | const fileName = path.basename(options.entry);
|
48 | const absolutePath = path.join(cwd, options.entry);
|
49 | const projectPath = path.dirname(absolutePath);
|
50 | const distPath =
|
51 | (options.dist && path.join(cwd, options.dist)) ||
|
52 | path.join(projectPath + "/dist/");
|
53 |
|
54 | if (isHot) {
|
55 | options.port = await getValidPort(options.port);
|
56 | }
|
57 |
|
58 | return Object.assign({}, options, {
|
59 | isHot,
|
60 | isDev,
|
61 | isPro,
|
62 | notPro,
|
63 | fileName,
|
64 | absolutePath,
|
65 | projectPath,
|
66 | distPath
|
67 | });
|
68 | }
|
69 |
|
70 | function beforeOption(inputs) {
|
71 | require("./before/compatibility")(inputs);
|
72 | }
|
73 |
|
74 | function createWebpackOption(inputs) {
|
75 | const entry = require("./options/entry")(inputs);
|
76 | const alias = require("./options/alias")(inputs);
|
77 | const externals = require("./options/externals")(inputs);
|
78 | const plugins = require("./options/plugins")(inputs);
|
79 | const rules = require("./options/rules")(inputs);
|
80 | const devServer = require("./options/devServer")(inputs);
|
81 | const publicPath = require("./options/publicPath")(inputs);
|
82 |
|
83 | const chunkFilename = `${inputs.fileName}.chunk.[name].js`;
|
84 |
|
85 | return {
|
86 | entry,
|
87 | target: inputs.target,
|
88 | mode: inputs.isPro ? PRODUCTION : DEVELOPMENT,
|
89 | output: {
|
90 | filename: inputs.hash ? "[name].bundle.[hash:5].js" : "[name].bundle.js",
|
91 | path: inputs.distPath,
|
92 | chunkFilename,
|
93 | publicPath
|
94 | },
|
95 | resolve: { alias, extensions: [".js", ".vue", ".jsx", ".json"] },
|
96 | module: { rules },
|
97 | externals,
|
98 | plugins,
|
99 | devServer
|
100 | };
|
101 | }
|
102 |
|
103 | function beforeCompiler(ctx, options) {
|
104 | require("./before/createDev")(ctx, options);
|
105 | require("./before/log")(ctx, options);
|
106 | }
|
107 |
|
108 | function afterCompiler(ctx) {
|
109 | return function() {
|
110 | const buildTime = getTimeFromDate(new Date());
|
111 | const context = { buildTime, ...ctx };
|
112 | require("./after/append")(context);
|
113 | require("./after/print")(context);
|
114 | };
|
115 | }
|
116 |
|
117 | const exec = after => (err, stats) => {
|
118 | if (err) {
|
119 | console.error(err.stack || err);
|
120 | if (err.details) console.error(err.details);
|
121 | return;
|
122 | }
|
123 | const hasError = stats.hasErrors();
|
124 | const buildInfo = stats.toString({
|
125 | chunks: false,
|
126 | colors: true,
|
127 | assets: false,
|
128 | chunkModules: false,
|
129 | chunks: false,
|
130 | children: false,
|
131 | maxModules: 1
|
132 | });
|
133 | console.log(buildInfo);
|
134 | if (!hasError) {
|
135 | after(buildInfo);
|
136 | }
|
137 | };
|
138 |
|
139 | function run(ctx, options) {
|
140 | const { port } = ctx;
|
141 | const { devServer } = options;
|
142 | const compiler = webpack(options);
|
143 |
|
144 | const package = {
|
145 | dev: () =>
|
146 | compiler.watch(
|
147 | { aggregateTimeout: 300, poll: 1000, ignored: /node_modules/ },
|
148 | exec(afterCompiler(ctx))
|
149 | ),
|
150 | pro: () => compiler.run(exec(afterCompiler(ctx))),
|
151 | hot: () => {
|
152 | const WebpackDevServer = load("webpack-dev-server");
|
153 | const url = `http://127.0.0.1:${port}/dev.html`;
|
154 |
|
155 | WebpackDevServer.addDevServerEntrypoints(options, devServer);
|
156 | new WebpackDevServer(compiler, devServer).listen(port, "localhost", () =>
|
157 | console.log(`${url}`.green)
|
158 | );
|
159 | setTimeout(() => {
|
160 | try {
|
161 | openBrowser({ url });
|
162 | } catch (err) {
|
163 | console.log("Chrome Not Found!");
|
164 | }
|
165 | }, 1000);
|
166 | }
|
167 | };
|
168 | package[ctx.env]();
|
169 | }
|
170 |
|
171 | async function getValidPort(port = 9999) {
|
172 | const min = port - 20;
|
173 | for (let i = port; i >= min; i--) {
|
174 | let url = `http://localhost:${i}`;
|
175 | const result = await axios
|
176 | .get(url)
|
177 | .then(() => false)
|
178 | .catch(() => true);
|
179 | if (result) {
|
180 | return Promise.resolve(i);
|
181 | }
|
182 | }
|
183 | }
|