diff --git a/.babelrc b/.babelrc deleted file mode 100644 index d143668..0000000 --- a/.babelrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - presets: ["es2015", "react", "stage-0"], - plugins: ["transform-class-properties"], -} diff --git a/.editorconfig b/.editorconfig index 1d83dab..e717f5e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,14 +1,13 @@ -# This file is for unifying the coding style for different editors and IDEs -# editorconfig.org +# http://editorconfig.org root = true [*] +indent_style = space +indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -indent_style = space -[*.json] -indent_style = space -indent_size = 2 +[*.md] +trim_trailing_whitespace = false diff --git a/.eslintignore b/.eslintignore index 4da523f..486fd55 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,7 +1,10 @@ -.publish/* dist/* -example/assets/* -lib/* +example/* +test/* node_modules/* -coverage/* -webpack.* +config/* +scripts/* +Gruntfile.js +package.json +webpack.*.js +src/utils/zipData.js diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index 4bde69c..0000000 --- a/.eslintrc +++ /dev/null @@ -1,12 +0,0 @@ -{ - "parser": "babel-eslint", - "extends": "airbnb", - "rules": { - "no-param-reassign": 0, - "react/sort-comp": 0, - }, - "env": { - "browser": true, - "mocha": true, - }, -} diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..869d56e --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,104 @@ +module.exports = { + 'extends': 'eslint-config-airbnb', + 'parser': 'babel-eslint', + 'env': { + 'browser': true, + 'node': true, + 'es6': true, + 'jest': true + }, + 'ecmaFeatures': { + 'jsx': true + }, + 'rules': { + 'react/jsx-no-bind': 'error', + 'react/no-multi-comp': 'off', + 'no-restricted-syntax': [ + 'error', + 'DebuggerStatement', + 'ForInStatement', + 'WithStatement' + ], + 'newline-after-var': ['error', 'always'], + 'newline-before-return': 'error', + 'comma-dangle': ['error', 'always-multiline'], // https://github.com/airbnb/javascript/commit/788208295469e19b806c06e01095dc8ba1b6cdc9 + 'indent': ['error', 2, {'SwitchCase': 1}], + 'no-console': 0, + 'no-alert': 0, + 'no-underscore-dangle': 'off', + 'max-len': [ 'error', 150, 2, { 'ignoreUrls': true, 'ignoreComments': false, } ], + 'react/require-default-props': 'off', + 'react/jsx-curly-spacing': [ 'error', 'always', { 'allowMultiline': true } ], + 'arrow-body-style': 'off', + 'no-mixed-operators': ['error', { + 'groups': [ + ['&', '|', '^', '~', '<<', '>>', '>>>'], + ['==', '!=', '===', '!==', '>', '>=', '<', '<='], + ['&&', '||'], + ['in', 'instanceof'] + ], + 'allowSamePrecedence': true + } ], + 'react/jsx-filename-extension': ['error', { 'extensions': ['.js', '.jsx'] }], + 'react/no-string-refs': 'off', + 'arrow-parens': ['error', 'always'], + 'jsx-a11y/no-static-element-interactions': 'off', + 'react/prefer-stateless-function': 'off', + 'no-param-reassign': 'off', + 'no-unused-vars': ['error', { 'ignoreRestSiblings': true }], + 'import/no-unresolved': [2, { ignore: ['react', 'react-dom', 'react-twzipcode'] }], + 'import/extensions': 'off', + 'import/no-extraneous-dependencies': ['error', { + devDependencies: [ + 'test/**', // tape, common npm pattern + 'tests/**', // also common npm pattern + 'spec/**', // mocha, rspec-like pattern + '**/__tests__/**', // jest pattern + '**/__mocks__/**', // jest pattern + 'test.js', // repos with a single test file + 'test-*.js', // repos with multiple top-level test files + '**/*.test.js', // tests where the extension denotes that it is a test + '**/webpack.config.js', // webpack config + '**/webpack.config.*.js', // webpack config + '**/rollup.config.js', // rollup config + '**/gulpfile.js', // gulp config + '**/gulpfile.*.js', // gulp config + '**/Gruntfile', // grunt config + 'config/jest/**', + 'src/testUtils/**', + '*.js' + ], + optionalDependencies: false, + }], + indent: ['error', 2, { + SwitchCase: 1, + VariableDeclarator: 1, + outerIIFEBody: 1, + MemberExpression: 1, + // CallExpression: { + // parameters: null, + // }, + FunctionDeclaration: { + parameters: 1, + body: 1 + }, + FunctionExpression: { + parameters: 1, + body: 1 + } + }], + 'no-plusplus': ['error', { "allowForLoopAfterthoughts": true }] + }, + 'plugins': [ + 'react', + 'import', + 'security' + ], + 'globals': { + '__DEVELOPMENT__': true, + '__CLIENT__': true, + '__SERVER__': true, + '__DISABLE_SSR__': true, + '__DEVTOOLS__': true, + } +}; diff --git a/.gitignore b/.gitignore index 374c3a4..83a98d2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,36 +1,50 @@ -# Logs -logs -*.log - -# Runtime data -pids -*.pid -*.seed - -# Coverage tools -lib-cov -coverage -coverage.html -.cover* - -# Dependency directory -node_modules - -# Example build directory -example/dist -.publish - -# Editor and other tmp files -*.swp -*.un~ -*.iml -*.ipr -*.iws -*.sublime-* -.idea/ -*.DS_Store +### SublimeText ### +*.sublime-workspace +### JetBrains ### +.idea + +### OSX ### +.DS_Store +.AppleDouble +.LSOverride +Icon + +# Thumbnails +._* + +# Visual Studio +*.history +*.vscode +.vscode/ +.history/ + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + +### Windows ### +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# App specific + +dist/ +node_modules/ +.tmp +/src/main.js .grunt/ +.history/ npm-debug.log - -coverage +build/** +dist/** +example/** +test/** +coverage/* diff --git a/.travis.yml b/.travis.yml index 5444d46..ba7e419 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,10 @@ language: node_js +cache: + yarn: true + directories: + - node_modules node_js: - - "5.11.1" -after_success: npm run coverage -after_script: npm run coveralls + - "7.2.1" +before_script: yarn add react@^16.0.0 react-dom@^16.0.0 --peer +after_success: yarn run coverage +after_script: yarn run coveralls diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 788a941..0000000 --- a/Gruntfile.js +++ /dev/null @@ -1,159 +0,0 @@ -const mountFolder = (connect, dir) => - connect.static(require('path').resolve(dir)); - -const webpackExampleConfig = require('./webpack.example.config.js'); -const webpackDistConfig = require('./webpack.dist.config.js'); -const webpackDevConfig = require('./webpack.config.js'); - -module.exports = (grunt) => { - // Let *load-grunt-tasks* require everything - require('load-grunt-tasks')(grunt); - - // Read configuration from package.json - const folders = { - src: 'src', - test: 'test', - dist: 'dist', - example: 'example', - }; - - grunt.initConfig({ - folders, - - webpack: { - example: webpackExampleConfig, - dist: webpackDistConfig, - }, - - 'webpack-dev-server': { - options: { - hot: true, - port: 8000, - host: '0.0.0.0', - webpack: webpackDevConfig, - publicPath: '/assets/', - contentBase: './<%= folders.src %>/', - }, - - start: { - keepAlive: true, - }, - }, - - connect: { - options: { - port: 8000, - }, - - example: { - options: { - keepalive: true, - middleware: (connect) => - [mountFolder(connect, folders.example)] - , - }, - }, - }, - - open: { - options: { - delay: 500, - }, - dev: { - path: 'http://localhost:<%= connect.options.port %>/webpack-dev-server/', - }, - example: { - path: 'http://localhost:<%= connect.options.port %>/', - }, - }, - - copy: { - dist: { - files: [ - // includes files within path - { - flatten: true, - expand: true, - src: [ - '<%= folders.src %>/index.html', - ], - dest: '<%= folders.example %>/', - filter: 'isFile', - }, - { - flatten: true, - expand: true, - src: [ - '<%= folders.src %>/libphonenumber.js', - ], - dest: '<%= folders.dist %>/', - filter: 'isFile', - }, - { - flatten: true, - expand: true, - src: '<%= folders.dist %>/*.png', - dest: '<%= folders.example %>/', - }, - { - flatten: true, - expand: true, - src: '<%= folders.src %>/styles/*', - dest: '<%= folders.dist %>/styles/', - }, - { - flatten: true, - expand: true, - src: '<%= folders.src %>/images/*', - dest: '<%= folders.dist %>/images/', - }, - ], - }, - }, - - clean: { - example: { - files: [{ - dot: true, - src: [ - '<%= folders.example %>', - ], - }], - }, - dist: { - files: [{ - dot: true, - src: [ - '<%= folders.dist %>', - ], - }], - }, - }, - - 'gh-pages': { - options: { - base: 'example', - }, - src: ['**'], - }, - }); - - grunt.registerTask('publish:examples', ['gh-pages']); - - grunt.registerTask('serve', (target) => { - if (target === 'example') { - return grunt.task.run(['clean:dist', 'webpack:dist', - 'build', 'open:example', 'connect:example']); - } - - return grunt.task.run([ - 'open:dev', - 'webpack-dev-server', - ]); - }); - - grunt.registerTask('build', ['clean:example', 'copy', 'webpack:example']); - grunt.registerTask('build:dist', ['clean:dist', 'copy', 'webpack:dist']); - - grunt.registerTask('default', []); -}; diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..16a5df4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2017 Patrick Wang (patw) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/README.md b/README.md index c7f0cff..2d0547f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # React-TWzipcode +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![Build Status](https://travis-ci.org/patw0929/react-twzipcode.svg)](https://travis-ci.org/patw0929/react-twzipcode) [![npm version](https://badge.fury.io/js/react-twzipcode.svg)](http://badge.fury.io/js/react-twzipcode) [![Coverage Status](https://coveralls.io/repos/github/patw0929/react-twzipcode/badge.svg?branch=master)](https://coveralls.io/github/patw0929/react-twzipcode?branch=master) @@ -14,12 +15,19 @@ Live demo: [patw0929.github.io/react-twzipcode](http://patw0929.github.io/react- To build the examples locally, run: -``` +```bash npm install -npm run example +npm run start +``` + +or + +```bash +yarn +yarn start ``` -Then open [`localhost:8000`](http://localhost:8000) in a browser. +Then open [`localhost:3000`](http://localhost:3000) in a browser. ## Installation @@ -28,10 +36,16 @@ The easiest way to use react-twzipcode is to install it from NPM and include it You can also use the standalone build by including `dist/main.js` in your page. If you use this, make sure you have already included React, and it is available as a global variable. -``` +```bash npm install react-twzipcode --save ``` +or + +```bash +yarn add react-twzipcode +``` + ## Usage @@ -50,8 +64,19 @@ Please see the [Demo Page](http://patw0929.github.io/react-twzipcode/) **NOTE:** The source code for the component is in `src`. A UMD bundle is also built to `dist`, which can be included without the need for any build system. -To build, watch and serve the examples (which will also watch the component source), run `npm run example`. +To build, watch and serve the examples (which will also watch the component source), run `npm start` or `yarn start`. +If you want to build to the bundle file to `dist/` folder, please run: + +```bash +npm run build +``` + +or + +```bash +yarn run build +``` ## Contributing @@ -62,10 +87,17 @@ npm test npm run lint ``` +or + +```bash +yarn test +yarn run lint +``` + +And please remember **don't** bumping version in pull request commits. ## License MIT -Copyright (c) 2015-2016 [patw](https://patw.me). - +Copyright (c) 2015-2017 [patw](https://patw.me). diff --git a/config/.eslintrc b/config/.eslintrc new file mode 100644 index 0000000..50e96b2 --- /dev/null +++ b/config/.eslintrc @@ -0,0 +1,10 @@ +{ + "rules": { + "no-var": 0, + "comma-dangle": 0, + "quote-props": 0, + "object-shorthand": 0, + "no-multiple-empty-lines": 0, + "no-trailing-spaces": 0 + } +} diff --git a/config/babel.dev.js b/config/babel.dev.js new file mode 100644 index 0000000..ae1fb9f --- /dev/null +++ b/config/babel.dev.js @@ -0,0 +1,35 @@ +var path = require('path'); + +module.exports = { + // Don't try to find .babelrc because we want to force this configuration. + babelrc: false, + // This is a feature of `babel-loader` for webpack (not Babel itself). + // It enables caching results in OS temporary directory for faster rebuilds. + cacheDirectory: true, + presets: [ + // Latest stable ECMAScript features + ['latest'], + // JSX, Flow + require.resolve('babel-preset-react'), + ], + plugins: [ + // class { handleClick = () => { } } + require.resolve('babel-plugin-transform-class-properties'), + // { ...todo, completed: true } + require.resolve('babel-plugin-transform-object-rest-spread'), + // function* () { yield 42; yield 43; } + [require.resolve('babel-plugin-transform-regenerator'), { + // Async functions are converted to generators by babel-preset-latest + async: false, + }], + // Polyfills the runtime needed for async/await and generators + [require.resolve('babel-plugin-transform-runtime'), { + helpers: false, + polyfill: false, + regenerator: true, + // Resolve the Babel runtime relative to the config. + // You can safely remove this after ejecting: + moduleName: path.dirname(require.resolve('babel-runtime/package')), + }], + ], +}; diff --git a/config/babel.prod.js b/config/babel.prod.js new file mode 100644 index 0000000..691b499 --- /dev/null +++ b/config/babel.prod.js @@ -0,0 +1,38 @@ +var path = require('path'); + +module.exports = { + // Don't try to find .babelrc because we want to force this configuration. + babelrc: false, + presets: [ + // Latest stable ECMAScript features + ['latest', { es2015: { 'modules': false } }], + // JSX, Flow + require.resolve('babel-preset-react') + ], + plugins: [ + // class { handleClick = () => { } } + require.resolve('babel-plugin-transform-class-properties'), + // { ...todo, completed: true } + require.resolve('babel-plugin-transform-object-rest-spread'), + // function* () { yield 42; yield 43; } + [require.resolve('babel-plugin-transform-regenerator'), { + // Async functions are converted to generators by babel-preset-latest + async: false + }], + // Polyfills the runtime needed for async/await and generators + [require.resolve('babel-plugin-transform-runtime'), { + helpers: false, + polyfill: false, + regenerator: true, + // Resolve the Babel runtime relative to the config. + // You can safely remove this after ejecting: + moduleName: path.dirname(require.resolve('babel-runtime/package')) + }], + // Optimization: hoist JSX that never changes out of render() + // Disabled because of issues: + // * https://github.com/facebookincubator/create-react-app/issues/525 + // * https://phabricator.babeljs.io/search/query/pCNlnC2xzwzx/ + // TODO: Enable again when these issues are resolved. + // require.resolve('babel-plugin-transform-react-constant-elements') + ] +}; diff --git a/config/env.js b/config/env.js new file mode 100644 index 0000000..0ffd756 --- /dev/null +++ b/config/env.js @@ -0,0 +1,43 @@ +/* eslint-disable no-var, arrow-parens, prefer-template */ +// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be +// injected into the application via DefinePlugin in Webpack configuration. + +var REACT_APP = /^REACT_APP_/i; + +function getClientEnvironment(publicUrl) { + var NODE_ENV = JSON.stringify(process.env.NODE_ENV || 'development'); + + // cnyes customized. to align other projects, defining same variables as other projects. + var DEVELOPMENT = NODE_ENV === JSON.stringify('development'); + var SERVER = false; + var CLIENT = true; + var BUILD_NAME = JSON.stringify(process.env.BUILD_NAME || 'dev'); + + var processEnv = Object + .keys(process.env) + .filter(key => REACT_APP.test(key)) + .reduce((env, key) => { + env[key] = JSON.stringify(process.env[key]); // eslint-disable-line no-param-reassign + + return env; + }, { + // Useful for determining whether we’re running in production mode. + // Most importantly, it switches React into the correct mode. + 'NODE_ENV': NODE_ENV, + // Useful for resolving the correct path to static assets in `public`. + // For example, . + // This should only be used as an escape hatch. Normally you would put + // images into the `src` and `import` them in code to get their paths. + 'PUBLIC_URL': JSON.stringify(publicUrl), + 'BUILD_NAME': BUILD_NAME, + }); + + return { + 'process.env': processEnv, + __SERVER__: SERVER, + __CLIENT__: CLIENT, + __DEVELOPMENT__: DEVELOPMENT, + }; +} + +module.exports = getClientEnvironment; diff --git a/config/jest/cssTransform.js b/config/jest/cssTransform.js new file mode 100644 index 0000000..b34498e --- /dev/null +++ b/config/jest/cssTransform.js @@ -0,0 +1,15 @@ +// This is a custom Jest transformer turning style imports into empty objects. +// http://facebook.github.io/jest/docs/tutorial-webpack.html + +module.exports = { + process() { + return ` + const idObj = require('identity-obj-proxy'); + module.exports = idObj; + `; + }, + getCacheKey(fileData, filename) { // eslint-disable-line no-unused-vars + // The output is always the same. + return 'cssTransform'; + }, +}; diff --git a/config/jest/fileTransform.js b/config/jest/fileTransform.js new file mode 100644 index 0000000..b0e91f1 --- /dev/null +++ b/config/jest/fileTransform.js @@ -0,0 +1,11 @@ +const path = require('path'); + +// This is a custom Jest transformer turning file imports into filenames. +// http://facebook.github.io/jest/docs/tutorial-webpack.html + +module.exports = { + process(src, filename) { + // eslint-disable-next-line prefer-template + return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';'; + }, +}; diff --git a/config/jest/setup.js b/config/jest/setup.js new file mode 100644 index 0000000..be3ae7b --- /dev/null +++ b/config/jest/setup.js @@ -0,0 +1,33 @@ +import jsdom from 'jsdom'; + +window.__SERVER__ = false; +window.__DEVELOPMENT__ = false; + +// Define some html to be our basic document +// JSDOM will consume this and act as if we were in a browser +const DEFAULT_HTML = '
'; + +// Define some variables to make it look like we're a browser +// First, use JSDOM's fake DOM as the document +const doc = jsdom.jsdom(DEFAULT_HTML); + +global.document = doc; + +// Set up a mock window +global.window = doc.defaultView; + +// Allow for things like window.location +const mockGeolocation = { + getCurrentPosition: jest.fn(), + watchPosition: jest.fn() +}; + +global.navigator.geolocation = mockGeolocation; + +const DATE_TO_USE = new Date('2017-05-11'); +const _Date = Date; + +global.Date = jest.fn(() => DATE_TO_USE); +global.Date.UTC = _Date.UTC; +global.Date.parse = _Date.parse; +global.Date.now = _Date.now; diff --git a/config/jest/setupTestFramework.js b/config/jest/setupTestFramework.js new file mode 100644 index 0000000..0690625 --- /dev/null +++ b/config/jest/setupTestFramework.js @@ -0,0 +1,16 @@ +/* global jasmine:false */ +import Enzyme from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; + +Enzyme.configure({ adapter: new Adapter() }); + +if (process.env.CI) { + const jasmineReporters = require('jasmine-reporters'); // eslint-disable-line global-require + const junitReporter = new jasmineReporters.JUnitXmlReporter({ + savePath: 'testresults', + consolidateAll: false, + }); + + jasmine.getEnv().addReporter(junitReporter); +} + diff --git a/config/jest/transform.js b/config/jest/transform.js new file mode 100644 index 0000000..75f893c --- /dev/null +++ b/config/jest/transform.js @@ -0,0 +1,4 @@ +const babelDev = require('../babel.dev'); +const babelJest = require('babel-jest'); + +module.exports = babelJest.createTransformer(babelDev); diff --git a/config/paths.js b/config/paths.js new file mode 100644 index 0000000..784cdd7 --- /dev/null +++ b/config/paths.js @@ -0,0 +1,49 @@ +/* globals resolveOwn:false */ + +var path = require('path'); +var fs = require('fs'); + +// Make sure any symlinks in the project folder are resolved: +// https://github.com/facebookincubator/create-react-app/issues/637 +const appDirectory = fs.realpathSync(process.cwd()); + +function resolveApp(relativePath) { + return path.resolve(appDirectory, relativePath); +} + +// We support resolving modules according to `NODE_PATH`. +// This lets you use absolute paths in imports inside large monorepos: +// https://github.com/facebookincubator/create-react-app/issues/253. + +// It works similar to `NODE_PATH` in Node itself: +// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders + +// We will export `nodePaths` as an array of absolute paths. +// It will then be used by Webpack configs. +// Jest doesn’t need this because it already handles `NODE_PATH` out of the box. + +// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. +// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. +// https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 + +// eslint-disable-next-line vars-on-top +const nodePaths = (process.env.NODE_PATH || '') + .split(process.platform === 'win32' ? ';' : ':') + .filter(Boolean) + .filter((folder) => !path.isAbsolute(folder)) + .map(resolveApp); + +// config after eject: we're in ./config/ +module.exports = { + appBuild: resolveApp('example'), + appDist: resolveApp('dist'), + appPublic: resolveApp('public'), + appHtml: resolveApp('src/index.html'), + appExampleJs: resolveApp('src/example.js'), + appPackageJson: resolveApp('package.json'), + appSrc: resolveApp('src'), + yarnLockFile: resolveApp('yarn.lock'), + appNodeModules: resolveApp('node_modules'), + ownNodeModules: resolveApp('node_modules'), + nodePaths: nodePaths, +}; diff --git a/config/polyfills.js b/config/polyfills.js new file mode 100644 index 0000000..7e60150 --- /dev/null +++ b/config/polyfills.js @@ -0,0 +1,14 @@ +if (typeof Promise === 'undefined') { + // Rejection tracking prevents a common issue where React gets into an + // inconsistent state due to an error, but it gets swallowed by a Promise, + // and the user has no idea what causes React's erratic future behavior. + require('promise/lib/rejection-tracking').enable(); + window.Promise = require('promise/lib/es6-extensions.js'); +} + +// fetch() polyfill for making API calls. +require('whatwg-fetch'); + +// Object.assign() is commonly used with React. +// It will use the native implementation if it's present and isn't buggy. +Object.assign = require('object-assign'); diff --git a/config/webpack.config.dev.js b/config/webpack.config.dev.js new file mode 100644 index 0000000..c56f104 --- /dev/null +++ b/config/webpack.config.dev.js @@ -0,0 +1,108 @@ +/* + * Webpack development server configuration + * + * This file is set up for serving the webpack-dev-server, which will watch for changes and recompile as required if + * the subfolder /webpack-dev-server/ is visited. Visiting the root will not automatically reload. + */ + +var webpack = require('webpack'); +var paths = require('./paths'); +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); +var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); +var WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); +var getClientEnvironment = require('./env'); + +// Webpack uses `publicPath` to determine where the app is being served from. +// In development, we always serve from the root. This makes config easier. +var publicPath = '/'; +// `publicUrl` is just like `publicPath`, but we will provide it to our app +// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. +// Omit trailing shlash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz. +var publicUrl = ''; +// Get enrivonment variables to inject into our app. +var env = getClientEnvironment(publicUrl); + +module.exports = { + devtool: 'cheap-module-source-map', + entry: [ + require.resolve('react-dev-utils/webpackHotDevClient'), + require.resolve('./polyfills'), + paths.appExampleJs + ], + + output: { + path: paths.appBuild, + pathinfo: true, + filename: 'static/js/bundle.js', + publicPath: publicPath + }, + + externals: { + react: 'React', + 'react-dom': 'ReactDOM' + }, + + resolve: { + modules: [ + 'src', + 'node_modules', + ...paths.nodePaths, + ], + alias: { + 'react-twzipcode': './components/TWZipCode.js', + }, + // root: __dirname + '/src', + }, + module: { + rules: [ + { + test: /\.(js|jsx)$/, + loader: 'eslint-loader', + enforce: 'pre', + include: paths.appSrc, + }, + { + exclude: [ + /\.html$/, + /\.(js|jsx)$/, + /\.css$/, + /\.scss$/, + /\.json$/, + /\.png$/, + /\.svg$/ + ], + loader: 'url-loader', + options: { + limit: 10000, + name: 'static/media/[name].[hash:8].[ext]' + } + }, + { + test: /\.(js|jsx)$/, + include: paths.appSrc, + loader: 'babel-loader', + options: require('./babel.dev') // eslint-disable-line global-require + }, + ] + }, + + plugins: [ + new InterpolateHtmlPlugin({ + PUBLIC_URL: publicUrl, + }), + new HtmlWebpackPlugin({ + inject: true, + template: paths.appHtml, + }), + new webpack.DefinePlugin(env), + new webpack.HotModuleReplacementPlugin(), + new CaseSensitivePathsPlugin(), + new WatchMissingNodeModulesPlugin(paths.appNodeModules), + ], + node: { + fs: 'empty', + net: 'empty', + tls: 'empty' + } +}; diff --git a/config/webpack.config.prod.js b/config/webpack.config.prod.js new file mode 100644 index 0000000..61f4d2c --- /dev/null +++ b/config/webpack.config.prod.js @@ -0,0 +1,166 @@ +/* eslint-disable no-var, arrow-parens, prefer-template, comma-dangle, object-shorthand, global-require, func-names, no-else-return, vars-on-top */ + +var webpack = require('webpack'); +var paths = require('./paths'); +var HtmlWebpackPlugin = require('html-webpack-plugin'); +var ExtractTextPlugin = require('extract-text-webpack-plugin'); +var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); +var getClientEnvironment = require('./env'); + +// Webpack uses `publicPath` to determine where the app is being served from. +// In development, we always serve from the root. This makes config easier. +var publicPath = ''; +// `publicUrl` is just like `publicPath`, but we will provide it to our app +// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript. +// Omit trailing shlash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz. +var publicUrl = ''; +// Get enrivonment variables to inject into our app. +var env = getClientEnvironment(publicUrl); + +// Assert this just to be safe. +// Development builds of React are slow and not intended for production. +if (env['process.env'].NODE_ENV !== '"production"') { + throw new Error('Production builds must have NODE_ENV=production.'); +} + +module.exports = { + // Don't attempt to continue if there are any errors. + bail: true, + devtool: false, + entry: { + main: './src/components/TWZipCode.js', + example: [ + require.resolve('./polyfills'), + './src/example.js', + ], + }, + + output: { + path: paths.appBuild, + pathinfo: true, + filename: '[name].js', + publicPath: publicPath, + library: 'TWZipCode', + libraryTarget: 'umd' + }, + + externals: { + 'react': { + root: 'React', + commonjs2: 'react', + commonjs: 'react', + amd: 'react' + }, + 'react-dom': { + root: 'ReactDOM', + commonjs2: 'react-dom', + commonjs: 'react-dom', + amd: 'react-dom' + }, + 'prop-types': { + root: 'PropTypes', + commonjs2: 'prop-types', + commonjs: 'prop-types', + amd: 'prop-types' + }, + }, + + resolve: { + modules: [ + 'src', + 'node_modules', + ...paths.nodePaths, + ], + alias: { + 'react-twzipcode': './components/TWZipCode.js', + }, + }, + module: { + rules: [ + { + test: /\.(js|jsx)$/, + loader: 'eslint-loader', + enforce: 'pre', + include: paths.appSrc, + }, + { + exclude: [ + /\.html$/, + /\.(js|jsx)$/, + /\.css$/, + /\.scss$/, + /\.json$/, + /\.png$/, + /\.svg$/ + ], + loader: 'url-loader', + options: { + limit: 10000, + name: 'media/[name].[hash:8].[ext]' + } + }, + { + test: /\.(js|jsx)$/, + include: paths.appSrc, + loader: 'babel-loader', + options: require('./babel.prod') + }, + ] + }, + + plugins: [ + new webpack.LoaderOptionsPlugin({ + minimize: true, + debug: false + }), + new InterpolateHtmlPlugin({ + PUBLIC_URL: publicUrl, + }), + // Generates an `index.html` file with the - - - - - -npm install --save react-twzipcode
-
- import React, { Component } from 'react';
+react-twzipcode react-twzipcode
範例
安裝
yarn add react-smartbanner
或
npm install --save react-twzipcode
語法
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import TWzipcode from 'react-twzipcode';
class App extends Component {
- handleChange (data) {
+ handleChange = (data) => {
console.log(data);
- }
+ };
render() {
return (
<div>
- <TWzipcode css={['form-control county-sel', 'form-control district-sel', 'form-control zipcode']}
+ <TWzipcode
+ css={['form-control county-sel', 'form-control district-sel', 'form-control zipcode']}
handleChangeCounty={this.handleChange}
handleChangeDistrict={this.handleChange}
- handleChangeZipcode={this.handleChange} />
+ handleChangeZipcode={this.handleChange}
+ />
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
-
-
-
-
- 參數
-
-
-
-
-
- 名稱
- 預設值
- 說明
-
-
-
-
- countyFieldName
- county
- 縣市下拉選單欄位名稱
-
-
- countyValue
- ''
- 縣市預設值
-
-
- css
- ['county-sel', 'district-sel', 'zipcode']
- 表單元件樣式名稱。依序是「縣市」、「區域」、「郵遞區號」。
-
-
- detect
- false
- 是否開啟 Geolocation 偵測經緯度並轉換為行政區的功能
-
-
- googleMapsKey
- ''
- Google API 金鑰。若您達到 Geocoder 的每日限制用量,可透過購買來提高用量,同時需設置金鑰與打開 detect
。
-
-
- districtFieldName
- district
- 區域下拉選單欄位名稱
-
-
- districtValue
- ''
- 區域預設值
-
-
- zipcodeFieldName
- zipcode
- 郵遞區號文字框欄位名稱
-
-
- zipcodeValue
- ''
- 郵遞區號預設值(優先權高於 countyValue
及 districtValue
)
-
-
- handleChangeCounty
- null
- 選擇縣市後的 callback
-
-
- handleChangeDistrict
- null
- 選擇鄉鎮市區後的 callback
-
-
- handleChangeZipcode
- null
- 改變郵遞區號後的 callback
-
-
- zipcodePlaceholder
- ''
- 郵遞區號欄位的 placeholder
-
-
-
-
-
-
- 特別感謝
-
- jQuery 版作者 @essoduke 以及其專案 jQuery-TWzipcode。
-
-
-
- 授權
-
- MIT 授權。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
名稱 | 預設值 | 說明 |
---|---|---|
countyFieldName | county | 縣市下拉選單欄位名稱 |
countyValue | '' | 縣市預設值 |
css | ['county-sel', 'district-sel', 'zipcode'] | 表單元件樣式名稱。依序是「縣市」、「區域」、「郵遞區號」。 |
detect | false | 是否開啟 Geolocation 偵測經緯度並轉換為行政區的功能 |
googleMapsKey | '' | Google API 金鑰。若您達到 Geocoder 的每日限制用量,可透過購買來提高用量,同時需設置金鑰與打開 detect 。 |
districtFieldName | district | 區域下拉選單欄位名稱 |
districtValue | '' | 區域預設值 |
zipcodeFieldName | zipcode | 郵遞區號文字框欄位名稱 |
zipcodeValue | '' | 郵遞區號預設值(優先權高於 countyValue 及 districtValue ) |
handleChangeCounty | null | 選擇縣市後的 callback |
handleChangeDistrict | null | 選擇鄉鎮市區後的 callback |
handleChangeZipcode | null | 改變郵遞區號後的 callback |
zipcodePlaceholder | '' | 郵遞區號欄位的 placeholder |
jQuery 版作者 @essoduke 以及其專案 jQuery-TWzipcode。
MIT 授權。