diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e884fef --- /dev/null +++ b/.gitignore @@ -0,0 +1,306 @@ +# dependencies +/node_modules + +# production +/dist + +# misc +.DS_Store +.vscode +.ideal + +npm-debug.log* +yarn-debug.log* +yarn-error.log* +yarn.lock +package-lock.json + + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + + + +# Built application files +*.apk +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ + + +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xccheckout +*.xcscmblueprint + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + + +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData/ + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ + +## Other +*.moved-aside +*.xccheckout +*.xcscmblueprint + +## Obj-C/Swift specific +*.hmap +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ \ No newline at end of file diff --git a/JSBridgeH5/.gitignore b/JSBridgeH5/.gitignore new file mode 100644 index 0000000..c151b98 --- /dev/null +++ b/JSBridgeH5/.gitignore @@ -0,0 +1,16 @@ +# dependencies +/node_modules + +# production +/dist + +# misc +.DS_Store +.vscode +.ideal + +npm-debug.log* +yarn-debug.log* +yarn-error.log* +yarn.lock +package-lock.json \ No newline at end of file diff --git a/JSBridgeH5/README.md b/JSBridgeH5/README.md new file mode 100644 index 0000000..b8fbe4c --- /dev/null +++ b/JSBridgeH5/README.md @@ -0,0 +1,45 @@ +# JSBridgeH5 +`JSBridge` 实例中 `H5` 端代码实现。 + +此项目使用 react 进行编写,但是关于 react 的代码不多,其中关于 jsbridge 使用的代码在任意项目或者框架中均可使用。 + +代码中有详细注释。 + + +## 下载项目 +``` bash +git clone https://github.com/beichensky/jsbridge-example.git +``` + +## 安装插件 +``` bash +# 打开 JSBridgeH5 项目 +cd JSBridgeH5 + +# 安装依赖 +npm install +``` + + +## 运行项目 +``` bash +npm run start +``` + +打开浏览器,输入:`localhost:8000` 查看项目运行效果。 + +运行项目后,才能在原生应用中展示,否则移动端 Webview 加载不出来页面。请记得在将当前的 ip 地址和端口替换到移动端 url 上。 + + + +## 项目打包 +``` bash +npm run build +``` + +新增 `dist` 目录,里面是打包好的项目代码 + + + +## 更多 +关于项目的更多介绍请查看我的博客:[使用 JSBridge 与原生 IOS、Android 进行交互(含H5、Android、IOS端代码,附 Demo)]() diff --git a/JSBridgeH5/babel.config.js b/JSBridgeH5/babel.config.js new file mode 100644 index 0000000..21b576f --- /dev/null +++ b/JSBridgeH5/babel.config.js @@ -0,0 +1,69 @@ +module.exports = (api) => { + api.cache(true); + + return { + presets: [ + "@babel/preset-env", + "@babel/preset-react" + ], + plugins: [ + [ + "@babel/plugin-proposal-decorators", + { + "legacy": true + } + ], + [ + "import", + { + "libraryName": "antd", + "style": true + } + ], + [ + "@babel/plugin-transform-runtime", + { + "corejs": 2 + } + ], + [ + "@babel/plugin-proposal-class-properties", + { + "loose": true + } + ], + "@babel/plugin-syntax-dynamic-import", + // "@babel/plugin-syntax-import-meta", + // 可以用 const ex = "before + // after"; 这种方式编写字符串 + // "@babel/plugin-proposal-json-strings", + // 可以使用 generate 语法 + // "@babel/plugin-proposal-function-sent", + // 可以使用 export * 这种命名空间的方式导出模块 + "@babel/plugin-proposal-export-namespace-from", + // 可以使用数字分离器书写数字 + // "@babel/plugin-proposal-numeric-separator" + // 可以使用异常抛出表达式, + "@babel/plugin-proposal-throw-expressions", + // 默认导出 + "@babel/plugin-proposal-export-default-from", + // 可以使用逻辑赋值运算符 + "@babel/plugin-proposal-logical-assignment-operators", + // 可以使用可选链的方式访问深层嵌套的属性或者函数 ?. + "@babel/plugin-proposal-optional-chaining", + // 可以使用管道运算符 |> + [ + "@babel/plugin-proposal-pipeline-operator", + { + "proposal": "minimal" + } + ], + // 可以使用空值合并语法 ?? + "@babel/plugin-proposal-nullish-coalescing-operator", + // 可以使用 do 表达式(可以认为是三元运算符的复杂版本) + "@babel/plugin-proposal-do-expressions", + // 可以使用功能绑定语法 obj::func + "@babel/plugin-proposal-function-bind" + ] + } +} diff --git a/JSBridgeH5/package.json b/JSBridgeH5/package.json new file mode 100644 index 0000000..5864283 --- /dev/null +++ b/JSBridgeH5/package.json @@ -0,0 +1,64 @@ +{ + "name": "jsbridge-h5", + "version": "1.0.0", + "description": "", + "private": true, + "scripts": { + "start": "webpack-dev-server --config webpack/webpack.config.dev.js", + "build": "webpack --config webpack/webpack.config.prod.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/core": "^7.0.0", + "@babel/plugin-proposal-class-properties": "^7.0.0", + "@babel/plugin-proposal-decorators": "^7.0.0", + "@babel/plugin-proposal-do-expressions": "^7.0.0", + "@babel/plugin-proposal-export-default-from": "^7.0.0", + "@babel/plugin-proposal-export-namespace-from": "^7.0.0", + "@babel/plugin-proposal-function-bind": "^7.0.0", + "@babel/plugin-proposal-logical-assignment-operators": "^7.0.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-proposal-optional-chaining": "^7.0.0", + "@babel/plugin-proposal-pipeline-operator": "^7.0.0", + "@babel/plugin-proposal-throw-expressions": "^7.0.0", + "@babel/plugin-syntax-dynamic-import": "^7.0.0", + "@babel/plugin-transform-runtime": "^7.0.0", + "@babel/preset-env": "^7.0.0", + "@babel/preset-react": "^7.0.0", + "@babel/runtime-corejs2": "^7.0.0", + "autoprefixer": "^9.5.1", + "babel-loader": "^8.0.0", + "babel-plugin-import": "^1.11.0", + "clean-webpack-plugin": "^2.0.1", + "css-loader": "^2.1.1", + "csv-loader": "^3.0.2", + "file-loader": "^3.0.1", + "friendly-errors-webpack-plugin": "^1.7.0", + "html-loader": "^0.5.5", + "html-webpack-plugin": "^3.2.0", + "less": "^3.9.0", + "less-loader": "^4.1.0", + "markdown-loader": "^5.0.0", + "mini-css-extract-plugin": "^0.5.0", + "optimize-css-assets-webpack-plugin": "^5.0.1", + "postcss-loader": "^3.0.0", + "style-loader": "^0.23.1", + "uglifyjs-webpack-plugin": "^2.1.2", + "webpack": "^4.29.6", + "webpack-cli": "^3.3.0", + "webpack-dev-server": "^3.1.4", + "webpack-merge": "^4.2.1", + "xml-loader": "^1.2.1" + }, + "dependencies": { + "antd": "^3.16.2", + "js-cookie": "^2.2.0", + "moment": "^2.24.0", + "prop-types": "^15.7.2", + "qs": "^6.7.0", + "react": "^16.8.6", + "react-dom": "^16.8.6" + } +} diff --git a/JSBridgeH5/public/favicon.ico b/JSBridgeH5/public/favicon.ico new file mode 100644 index 0000000..a11777c Binary files /dev/null and b/JSBridgeH5/public/favicon.ico differ diff --git a/JSBridgeH5/public/index.html b/JSBridgeH5/public/index.html new file mode 100644 index 0000000..7a4509e --- /dev/null +++ b/JSBridgeH5/public/index.html @@ -0,0 +1,12 @@ + + + + + + + JSBridge Demo + + +
+ + \ No newline at end of file diff --git a/JSBridgeH5/src/App.js b/JSBridgeH5/src/App.js new file mode 100644 index 0000000..70fa41d --- /dev/null +++ b/JSBridgeH5/src/App.js @@ -0,0 +1,79 @@ +import React, { Component, useState } from 'react'; +import { Button, Input, message } from 'antd'; +import Cookie from 'js-cookie'; + +// 使用 CSS Module 的方式引入 App.less +import styles from './App.less'; + + +const isAndroid = navigator.userAgent.endsWith('android'); + +export default (props) => { + + const [user, setUser] = useState(); + const [name, setName] = useState(); + const [token, setToken] = useState(Cookie.get('token')); + + window.setupWebViewJavascriptBridge(bridge => { + bridge.registerHandler("changeName", (data, fn) => { + setName(data); + fn && fn(""); + }); + + bridge.registerHandler("syncCookie", (data, fn) => { + setToken(Cookie.get('token')); + fn && fn(""); + }); + }); + + /** + * 调用原生的默认接受方法 + */ + const handleInit = () => { + window.setupWebViewJavascriptBridge(bridge => { + bridge.send("JS 传递给原生的消息", (data) => { + message.success(data); + }) + }) + }; + + /** + * 调用原生方法刷新 H5 界面 + */ + const handleReload = () => { + window.setupWebViewJavascriptBridge(bridge => { + bridge.callHandler('reloadUrl'); + }) + }; + + /** + * 修改原生界面的 User 名称 + */ + const handleChangeUser = () => { + window.setupWebViewJavascriptBridge(bridge => { + bridge.callHandler('changeUser', user, () => { + message.success('user 修改成功!'); + setUser(''); + }); + }) + }; + + return ( +
+

H5 界面:

+ { isAndroid ? : null} +
+ +
+ + setUser(e.target.value) } style={{ marginLeft: 10, width: 160 }} placeholder="请输入新的 user 名称" /> +
+ { name } +
+
+ { token } +
+
+ ) + +} diff --git a/JSBridgeH5/src/App.less b/JSBridgeH5/src/App.less new file mode 100644 index 0000000..4a1fdab --- /dev/null +++ b/JSBridgeH5/src/App.less @@ -0,0 +1,4 @@ +.app { + padding: 20px; + background: #f0f0f0; +} \ No newline at end of file diff --git a/JSBridgeH5/src/index.js b/JSBridgeH5/src/index.js new file mode 100644 index 0000000..989e0d4 --- /dev/null +++ b/JSBridgeH5/src/index.js @@ -0,0 +1,91 @@ +import React from 'react'; +import ReactDom from 'react-dom'; +import { LocaleProvider, message } from 'antd'; +import zh_CN from 'antd/lib/locale-provider/zh_CN'; +import 'moment/locale/zh-cn'; + +import App from './App'; + +/** + * 使用 JSBridge 总结: + * 1、跟 IOS 交互的时候,只需要且必须注册 iosFuntion 方法即可, + * 不能在 setupWebViewJavascriptBridge 中执行 bridge.init 方法,否则 IOS 无法调用到 H5 的注册函数; + * 2、与安卓进行交互的时候 + * ①、使用 iosFuntion,就可以实现 H5 调用 安卓的注册函数,但是安卓无法调用 H5 的注册函数, + * 并且 H5 调用安卓成功后的回调函数也无法执行 + * ②、使用 andoirFunction 并且要在 setupWebViewJavascriptBridge 中执行 bridge.init 方法, + * 安卓才可以正常调用 H5 的回调函数,并且 H5 调用安卓成功后的回调函数也可以正常执行了 + */ + +const isAndroid = navigator.userAgent.endsWith('android'); + +/** + * Android 与安卓交互时: + * 1、不调用这个函数安卓无法调用 H5 注册的事件函数; + * 2、但是 H5 可以正常调用安卓注册的事件函数; + * 3、还必须在 setupWebViewJavascriptBridge 中执行 bridge.init 方法,否则: + * ①、安卓依然无法调用 H5 注册的事件函数 + * ①、H5 正常调用安卓事件函数后的回调函数无法正常执行 + * + * @param {*} callback + */ +const andoirFunction = (callback) => { + if (window.WebViewJavascriptBridge) { + callback(window.WebViewJavascriptBridge); + } else { + document.addEventListener('WebViewJavascriptBridgeReady', function () { + callback(window.WebViewJavascriptBridge); + }, false) + } +} + +/** + * IOS 与 IOS 交互时,使用这个函数即可,别的操作都不需要执行 + * @param {*} callback + */ +const iosFuntion = (callback) => { + if (window.WebViewJavascriptBridge) { return callback(window.WebViewJavascriptBridge) } + if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback) } + window.WVJBCallbacks = [callback]; + var WVJBIframe = document.createElement('iframe'); + WVJBIframe.style.display = 'none'; + WVJBIframe.src = 'wvjbscheme://__BRIDGE_LOADED__'; + document.documentElement.appendChild(WVJBIframe); + setTimeout(function(){ + document.documentElement.removeChild(WVJBIframe); + }, 0); +} + +/** + * 注册 setupWebViewJavascriptBridge 方法 + * 之所以不将上面两个方法融合成一个方法,是因为放在一起,那么就只有 iosFuntion 中相关的方法体生效 + */ +window.setupWebViewJavascriptBridge = isAndroid ? andoirFunction : iosFuntion; + +/** + * 这里如果不做判断是不是安卓,而是直接就执行下面的方法,就会导致 + * 1、IOS 无法调用 H5 这边注册的事件函数 + * 2、H5 可以正常调用 IOS 这边的事件函数,并且 H5 的回调函数可以正常执行 + */ +if (isAndroid) { + /** + * 与安卓交互时,不调用这个函数会导致: + * 1、H5 可以正常调用 安卓这边的事件函数,但是无法再调用到 H5 的回调函数 + * + * 前提 setupWebViewJavascriptBridge 这个函数使用的是 andoirFunction 这个,否则还是会导致上面 1 的现象出现 + */ + window.setupWebViewJavascriptBridge(function (bridge) { + // 注册 H5 界面的默认接收函数(与安卓交互时,不注册这个事件无法接收回调函数) + bridge.init(function (msg, responseCallback) { + message.success(msg); + responseCallback("JS 返回给原生的消息内容"); + }) + }) +} + +ReactDom.render( + + + , + document.querySelector('#root') +); \ No newline at end of file diff --git a/JSBridgeH5/webpack/webpack.common.js b/JSBridgeH5/webpack/webpack.common.js new file mode 100644 index 0000000..400009d --- /dev/null +++ b/JSBridgeH5/webpack/webpack.common.js @@ -0,0 +1,146 @@ +const path = require('path'); +const HTMLWebpackPlugin = require('html-webpack-plugin'); +const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const autoprefixer = require('autoprefixer'); + +const appSrc = path.resolve(__dirname, '../src'); +const appDist = path.resolve(__dirname, '../dist'); +const appPublic = path.resolve(__dirname, '../public'); +const appIndex = path.resolve(appSrc, 'index.js'); +const appHtml = path.resolve(appPublic, 'index.html'); + +module.exports = { + entry: { + main: [appIndex], + common: ['react', 'react-dom'] + }, + output: { + filename: 'public/js/[name].[hash:8].js', + path: appDist, + publicPath: '/' + }, + plugins: [ + // 自动在出口目录生成 html 并自动引入 js 文件 + new HTMLWebpackPlugin({ + template: appHtml, + filename: 'index.html' + }), + // 在命令行展示更清晰地提示信息 + new FriendlyErrorsWebpackPlugin() + ], + module: { + rules: [ + // 解析 js + { + test: /\.(js|jsx)$/, + loader: 'babel-loader?cacheDirectory', + include: [ appSrc ], + exclude: /node_modules/ + }, + // 解析样式 + { + test: /\.(css|less)$/, + exclude: /node_modules/, + use: [ + { + // 使用 MiniCssExtractPlugin.loader 代替 style-loader + loader: MiniCssExtractPlugin.loader + }, + { + loader: 'css-loader', + options: { + sourceMap: true, + modules: true, + localIdentName: '[local].[hash:8]' + } + }, + { + loader: 'postcss-loader', + options: { + plugins: () => [autoprefixer()] + } + }, + { + loader: 'less-loader', + options: { + javascriptEnabled: true + } + } + ] + }, + { + test: /\.(css|less)$/, + include: /node_modules/, + use: [ + { + loader: MiniCssExtractPlugin.loader + }, + { + loader: 'css-loader', + options: {} + }, + { + loader: 'postcss-loader', + options: { + plugins: () => [autoprefixer()] + } + }, + { + loader: 'less-loader', + options: { + javascriptEnabled: true + } + } + ] + }, + // 解析图片资源 + { + test: /\.(png|svg|jpg|gif)$/, + use: [ + 'file-loader' + ] + }, + // 解析 字体 + { + test: /\.(woff|woff2|eot|ttf|otf)$/, + use: [ + 'file-loader' + ] + }, + // 解析数据资源 + { + test: /\.(csv|tsv)$/, + use: [ + 'csv-loader' + ] + }, + // 解析数据资源 + { + test: /\.xml$/, + use: [ + 'xml-loader' + ] + }, + // 解析 MakeDown 文件 + { + test: /\.md$/, + use: [ + 'html-loader', + 'markdown-loader' + ] + } + ] + }, + resolve: { + // 设置别名 + alias: { + src: appSrc, + utils: path.resolve(__dirname, '../src/utils'), + pages: path.resolve(__dirname, '../src/pages'), + components: path.resolve(__dirname, '../src/components') + }, + // 设置模块查找范围 + modules: [path.resolve(__dirname, '../node_modules')] + } +} \ No newline at end of file diff --git a/JSBridgeH5/webpack/webpack.config.dev.js b/JSBridgeH5/webpack/webpack.config.dev.js new file mode 100644 index 0000000..085ebf6 --- /dev/null +++ b/JSBridgeH5/webpack/webpack.config.dev.js @@ -0,0 +1,49 @@ +const path = require('path'); +const webpack = require('webpack'); +const merge = require('webpack-merge'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const common = require('./webpack.common'); + +const appPublic = path.resolve(__dirname, '../public'); + +const config = merge(common, { + mode: 'development', + devtool: 'inline-source-map', + devServer: { + // 作为服务器发布的目录 + contentBase: appPublic, + // 热加载 + hot: true, + // host 地址,设置成 0.0.0.0 才能在移动端使用 ip 地址访问到 + host: '0.0.0.0', + // 端口号 + port: 8000, + historyApiFallback: true, + // 是否在浏览器蒙层展示错误信息 + overlay: true, + inline: true, + // 展示的统计信息 + stats: 'errors-only', + // 配置代理 + proxy: { + '/api': { + changeOrigin: true, + target: 'https://easy-mock.com/mock/5c2dc9665cfaa5209116fa40/example', + pathRewrite: { + '^/api/': '/' + } + } + } + }, + plugins: [ + // 热加载插件 + new webpack.HotModuleReplacementPlugin(), + // 提取 css 文件 + new MiniCssExtractPlugin({ + filename: 'public/styles/[name].css', + chunkFilename: 'public/styles/[name].chunk.css' + }) + ] +}); + +module.exports = config; \ No newline at end of file diff --git a/JSBridgeH5/webpack/webpack.config.prod.js b/JSBridgeH5/webpack/webpack.config.prod.js new file mode 100644 index 0000000..6d5b99e --- /dev/null +++ b/JSBridgeH5/webpack/webpack.config.prod.js @@ -0,0 +1,95 @@ +const webpack = require('webpack'); +const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); +const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const CleanWebpackPlugin = require('clean-webpack-plugin'); +const merge = require('webpack-merge'); +const common = require('./webpack.common'); + +const config = merge(common, { + mode: 'production', + devtool: 'hidden-source-map', + plugins: [ + // 提取 css 文件 + new MiniCssExtractPlugin({ + filename: 'public/styles/[name].[contenthash:8].css', + chunkFilename: 'public/styles/[name].[contenthash:8].chunk.css' + }), + // 区分环境 + new webpack.DefinePlugin({ + // 定义 NODE_ENV 环境变量为 production + 'process.env': { + NODE_ENV: JSON.stringify('production') + } + }), + // 清理 dist 文件,2.0。0 版本之后不需要设置参数就可以自动清除打包生成的目录 + new CleanWebpackPlugin() + ], + optimization: { + // 打包压缩js/css文件 + minimizer: [ + new UglifyJsPlugin({ + uglifyOptions: { + compress: { + // 在UglifyJs删除没有用到的代码时不输出警告 + warnings: false, + // 删除所有的 `console` 语句,可以兼容ie浏览器 + drop_console: true, + // 内嵌定义了但是只用到一次的变量 + collapse_vars: true, + // 提取出出现多次但是没有定义成变量去引用的静态值 + reduce_vars: true, + }, + output: { + // 最紧凑的输出 + beautify: false, + // 删除所有的注释 + comments: false, + } + } + }), + // 压缩 CSS 代码 + new OptimizeCSSAssetsPlugin({}) + ], + // 拆分公共模块 + splitChunks: { + cacheGroups: { + styles: { + name: 'styles', + test: /\.(css|less)/, + chunks: 'all', + enforce: true, + // 表示是否使用已有的 chunk + reuseExistingChunk: true + }, + commons: { + name: 'commons', + chunks: 'initial', + minChunks: 2, + reuseExistingChunk: true + }, + vendors: { + name: 'vendors', + test: /[\\/]node_modules[\\/]/, + priority: -10, + reuseExistingChunk: true + } + } + }, + // 为每个仅含有 runtime 的入口起点添加一个额外 chunk + runtimeChunk: true + }, + // 性能提醒 + performance: { + hints: false + }, + // 统计信息展示 + stats: { + modules: false, + children: false, + chunks: false, + chunkModules: false + } +}); + +module.exports = config; \ No newline at end of file