目录
react-sample-javascript
项目初始化
统一规范代码格式
安装依赖
项目配置
webpack 基础配置
Webpack 开发配置
项目进阶
生产环境配置
结语
参考
- react-sample-javascript
- React 16.0 boilerplate with react-router-dom, redux & webpack 4. (for javascript)
github 项目地址 https://github.com/Dante-dan/react-sample-javascript
项目初始化
统一规范代码格式
配置 .editorconfig 使得 IDE 的方式统一 (见代码)
配置 .eslintrc.js 使得代码规范统一 (见代码)
预期功能
管理资源: 能加载 css,sccc,less, 以及静态文件
管理输出: 将打包后的静态文件输出至 static 目录下, 以各自的文件类型管理
dev: 使用 source map, 方便调试时代码定位
dev: 配置 devServer, 并配置热替换, 热加载, 自动刷新, 自动打开浏览器, 并预留 proxyTable
dev: 设置默认打开 8080, 被占用则寻找下一个空接口
production: 代码分离, 打包 css 文件, css 代码压缩, js 代码压缩, 输出到模板 html, 配置 gzip
analysis:: 使用 BundleAnalyzerPlugin 分析打包后的性能
目录结构
首先使用 npm init 初始化一个包含 package.json 的根目录
- :.
- .babelrc #babel 的规则以及插件
- .editorconfig #IDE / 编辑器相关的配置
- .eslintignore #Eslint 忽视的目录
- .eslintrc.js #Eslint 的规则和插件
- .gitignore #Git 忽视的目录
- .postcssrc.js #postcss 的插件
- package-lock.json
- package.json #项目相关的包
- README.md
- yarn.lock
- build #webpack 相关的配置
- utils.js #webpack 配置中的通用方法
- webpack.base.conf.js #webpack 的基础配置
- webpack.dev.conf.js #webpack 的开发环境配置
- webpack.prod.conf.js #webpack 的生产环境配置
- src #主目录, 业务代码
- app.css
- App.js
- favicon.ico
- index.ejs
- index.js
- assets #静态目录, 存放静态资源
- config.json
- img
- logo.svg
安装依赖
- eslint-loader
- eslint
- eslint-config-airbnb
- eslint-plugin-import
- eslint-friendly-formatter
- eslint-plugin-flowtype
- eslint-plugin-jsx-a11y
- eslint-plugin-react
- babel-polyfill
- webpack
- jest
- friendly-errors-webpack-plugin
编译提示的 webpack 插件
html-webpack-plugin
新建 html 入口文件的 webpack 插件
copy-webpack-plugin
webpack 配置合并模块
webpack-merge
webpack 配置合并模块
- webpack-dev-server
- webpack-bundle-analyzer
- webpack-cli
portfinder 寻找接口的插件
- extract-text-webpack-plugin
- node-notifier
- optimize-css-assets-webpack-plugin
- autoprefixer
- mini-css-extract-plugin
- autoprefixer
- css-loader
- less-loader
- postcss-loader
- postcss-import
- postcss-loader
- style-loader
- babel-core
- babel-eslint
- babel-loader
- babel-plugin-transform-runtime
- babel-plugin-import
- babel-preset-env
- babel-preset-react
- babel-polyfill
- url-loader
- cross-env
- file-loader
- yarn add eslint eslint-loader eslint-config-airbnb eslint-plugin-import eslint-friendly-formatter eslint-plugin-flowtype eslint-plugin-jsx-a11y eslint-plugin-react babel-polyfill webpack jest webpack-merge copy-webpack-plugin html-webpack-plugin friendly-errors-webpack-plugin webpack-dev-server webpack-bundle-analyzer webpack-cli portfinder extract-text-webpack-plugin node-notifier optimize-css-assets-webpack-plugin autoprefixer mini-css-extract-plugin autoprefixer css-loader less-loader postcss-loader postcss-import postcss-loader style-loader babel-core babel-eslint babel-loader babel-plugin-transform-runtime babel-plugin-import babel-preset-env babel-preset-react babel-polyfill url-loader cross-env file-loader -D
项目配置
webpack 基础配置
为了控制开发环境和生产环境, 我们可以新建 build 文件夹. 分别书写开发环境和生产环境的 webpack 配置文件, 这样也更可以方便我们分别控制生产环境和开发环境.
为了提高代码的复用率, 也为了区别 基础配置 和 个性配置 , 可以分别新建 webpack.base,webpack.dev 和 webpack.prod 三个配置文件. 首先配置最基础的 entry(入口)和 output(出口).
- module.exports = {
- context: path.resolve(__dirname, '../'), // 绝对路径.__dirname 为当前目录.
- // 基础目录用于从配置中解析入口起点. 因为 webpack 配置在 build 下, 所以传入 '../'
- entry: {
- app: ('./src/index.js') // 项目的入口
- },
- output: {
- path: path.resolve(__dirname, '../dist'),
- filename: '[name].[hash:8].js',
- publicPath: '/',
- libraryTarget: 'umd',
- },
- }
- entry
entry 可以分别为字符串, 数组和对象.
倘若应用只有一个单一的入口, entry 的值可以使用任意类型, 不会影响输出结果.
- // entry 为字符串
- {
- entry: './src/index.js',
- output: {
- path: '/dist',
- filename: 'bundle.js'
- }
- }
- // 结果会生成 '/dist/bundle.js'
- // entry 为数组, 可以添加多个彼此不互相依赖的文件. 结合 output.library 选项, 如果传入数组, 则只导出最后一项.
- {
- // 如果你在 html 文件里引入了'bable-polyfill', 可以通过数组将它加到 bundle.js 的最后.
- entry: ['./src/index.js', 'babel-polyfill'] ,
- output:{
- path: '/dist',
- filename: 'bundle.js'
- }
- }
- // entry 为对象, 可以将页面配置为多页面的而不是 SPA, 有多个 html 文件. 通过对象告诉 webpack 为每个入口, 成一个 bundle 文件.
- // 多页面的配置, 可能还要借助于 HtmlWebpackPlugin, 来指定每个 html 需要引入的 js
- {
- entry: {
- index: './src/index.js'
- main: './src/index.js'
- login: './src/login.js'
- }
- output:{
- path: '/dist/pages'
- filename: '[name]-[hash:5].js' // 文件名取自'entry'对象的键名, 为了防止推送代码后浏览器读缓存, 故再生成的文件之后加上 hash 码.
- }
- }
- // 会分别生成 index.js,main.js,login.js 三个文件
关于 webpack 构建多页面 https://segmentfault.com/a/1190000004511992 可以参考这篇文章. 不过现在 webpack4.x 也是一次断崖式升级, 感兴趣的同学可以自行搜索.
- // entry 也可以传入混合类型
- {
- entry:{
- vendor: ['jquery','amap','babel-polyfill'] // 也可以借助 CommonsChunkPlugin 提取 vendor 的 chunk.
- index: './src/index.js'
- }
- output: {
- path: '/dist'
- filename: '[name]-[hash:5].js'
- }
- }
CommonsChunkPlugin 在 webpack4.0 之后移除了, 可以使用 splitChunksPlugin 代替.
可以参阅如下链接: optimization.splitChunks https://www.webpackjs.com/plugins/split-chunks-plugin/
output
output 最基础的两个配置为 path 和 filename :
path 告诉 webpack 的输出目录在那里, 一般我们会设置在根目录的 dist 文件夹;
filename 用于指定输出文件的文件名, 如果配置了创建了多个单独的 chunk 则可以使用[name].[hash] 这种占位符来确保每个文件有唯一的名称;
另一个常见配置 publicPath 则是用于更加复杂的场景. 举例: 在本地时, 你可能会使用 ../assets/test.png 这种 url 来载入图片. 而在生产环境下, 你可能会使用 CDN 或者图床的地址. 那么就需要配置
publicPath = "http://cdn.example.com/assets/"
来实现生产模式下编译输出文件时自动更新 url.
- output: {
- path: path.resolve(__dirname, '../dist'),
- filename: '[name].[hash:8].js',
- publicPath: '/',
- },
- resolve
resolve 常用的两个配置为 alias 和 extensions :
alias 创建 import 或者 require 的别名
extensins 自动解析文件拓展名, 补全文件后缀
- resolve: {
- // 自动解析文件扩展名 (补全文件后缀)(从左 -> 右)
- // import hello from './hello' (!hello.js? -> !hello.jsx? -> !hello.json)
- extensions: ['.js', '.jsx', '.json'],
- alias: {
- '@': resolve('src')
- }
- },
- module
module 的选项决定了如何处理项目中的不同类型的模块. 其中常用的有 rules 和 noParese 两个配置项.
noParese 是为了防止 weback 解析与所有与 rule 相匹配的文件. 目的是, 忽略大型的 library 可以提高构建性能.
- noParse: function(content) {
- return /jquery|lodash/.test(content);
- }
rules 用于在创建模块是, 匹配规则数组, 以确定哪些规则能够对 module 应用 loader, 或者是修改 parser.
- module: {
- rules: [
- {
- test: /\.(js|jsx)$/,
- exclude: /node_modules/,
- enforce: 'pre',
- use: [{
- loader: 'babel-loader',
- }, {
- loader: 'eslint-loader', // 指定启用 eslint-loader
- options: {
- formatter: require('eslint-friendly-formatter'),
- emitWarning: false
- }
- }]
- },
- {
- test: /\.css$/,
- include: /node_modules/,
- use: [
- MiniCssExtractPlugin.loader,
- 'css-loader',
- {
- loader: 'postcss-loader',
- options: {
- plugins: () => [autoprefixer({ browsers: 'last 5 versions' })],
- sourceMap: false,
- },
- },
- ],
- },
- {
- test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
- loader: 'url-loader',
- options: {
- limit: 10000,
- name: ('assets/img/[name].[hash:7].[ext]')
- }
- },
- {
- test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
- loader: 'url-loader',
- options: {
- limit: 10000,
- name: ('assets/media/[name].[hash:7].[ext]')
- }
- },
- {
- test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
- loader: 'url-loader',
- options: {
- limit: 10000,
- name: ('assets/fonts/[name].[hash:7].[ext]')
- }
- }
- ]
- }
例如上述代码, 就使用 eslint-lodaer 和 babel-loader 处理了除了 node_modules 以外的 js||jsx. 同时配置了, 解析图片, 视频, 字体文件等的解析, 当 rules 匹配到的文件时, 小于 10000 byte 时, 采用 url-loader 解析文件.(因为 base64 会让图片的体积变大, 所以当文件较大时, 使用 base64 并不明智)
Webpack 开发配置
因为在 webpack 4.X 中使用了流行的 "约定大于配置" 的做法, 所以在新加入配置项 mode , 可以告知 webpack 使用相应模式的内置优化.
选项 | 描述 |
---|---|
development | 会将 process.env.NODE_ENV 的值设为 development 。启用 NamedChunksPlugin 和 NamedMoudulesPlugin。 |
production | 会将 process.env.NODE_ENV 的值设为 production 。启用 FlagDependencyUsagePlugin,FlagIncludedChunksPlugin,ModuleConcatenationPlugin,NoEmitOnErrorsPlugin,OccurrenceOrderPlugin,SideEffectsFlagPlugin 和 UglifyJsPlugin。 |
如果我们只设置 NODE_ENV, 则不会自动设置 mode
在开发时, 我们往往希望能看到当前开发的页面, 并且能热加载. 这时, 我们可以借助 webpack-dev-server 这个插件, 来在项目中起一个应用服务器.
- // package.json
- "scripts": {
- "start": "webpack-dev-server --mode development --config build/webpack.dev.conf.js",
- }
- // 设置当前的 mode 为 development, 同样这个配置也可以写在 webpack.dev.conf.js 中. 然后使用 build 目录下的 webpack.dev.conf.js 来配置相关的 webpack.
- devServer: {
- clientLogLevel: 'warning',
- historyApiFallback: true, // 在开发单页应用时非常有用, 它依赖于 HTML5 history API, 如果设置为 true, 所有的跳转将指向 index.html
- contentBase: path.resolve(__dirname, '../src'),
- compress: true,
- hot: true, // 热加载
- inline: true, // 自动刷新
- open: true, // 自动打开浏览器
- host: HOST||'localhost',
- port: PORT,
- overlay: { warnings: false, errors: true }, // 在浏览器上全屏显示编译的 errors 或 warnings.
- publicPath: '/',
- proxy: {},
- quiet: true, // necessary for FriendlyErrorsPlugin // 终端输出的只有初始启动信息. webpack 的警告和错误是不输出到终端的
- watchOptions: {
- poll: false
- }
- },
- plugins: [
- new webpack.DefinePlugin({
- ...process.env
- }),
- // 开启 HMR(热替换功能, 替换更新部分, 不重载页面!)
- new webpack.HotModuleReplacementPlugin(),// HMR shows correct file names in console on update.
- // 显示模块相对路径
- new webpack.NamedModulesPlugin(),
- // 不显示错误信息
- new webpack.NoEmitOnErrorsPlugin(),
- // https://github.com/ampedandwired/html-webpack-plugin
- ]
其实在开发时, 我们可以设置
contentBase: '/src'
,contentBase 指定了 devServer 能访问的资源地址. 因为我们开发时, 资源大部分都放在 src 目录下, 所以可以直接指定资源路径为 src 目录. 因为我们在 webpack 基础配置时, 配置了 output 输出为 dist 目录, 所以我们也可以在 devServer 里, 设置 contentBase 为 dist 目录. 不过此时需要使用 copyWebpackPlugin 将一些静态资源复制到 dist 目录下, 手动新建 dist 目录, 并复制也可以.
另外, 当使用 history 路由时, 要配置
historyApiFallback = true
, 以此让服务器放弃路由权限, 交由前端路由. 而是用 hash 路由则不需要此配置.
项目进阶
生产环境配置
在使用 webpack 4.x 的 mode 配置之后, 需要我们手动配置的项已经减少了很多, 像 js 代码压缩这种工具 UglifyJsPlugin 就已经不用手动去配置. 但是像很多前面提到的 代码分离 ,
css 代码提取和压缩
,html 的生成 以及 复制静态资源 还需要我们手动配置.
代码分离
- // 设置代码分离的输出目录
- output: {
- path: path.resolve(__dirname, '../dist'),
- filename: ('js/[name].[hash:8].js'),
- chunkFilename: ('js/[name]-[id].[hash:8].js')
- },
- // 代码分离
- optimization: {
- runtimeChunk: {
- name: "manifest"
- },
- splitChunks: {
- chunks: 'all'
- }
- },
可以参阅如下链接: optimization.splitChunks https://www.webpackjs.com/plugins/split-chunks-plugin/
css 代码压缩
借助
MiniCssExtractPlugin
来实现压缩 css 和提取 css. 因为
MiniCssExtractPlugin
无法与 style-loader 共存, 所以我们需要判断当前环境是生成环境还是开发环境.
我们可以新建一个 util.js 的文件, 在 webpack 当中一些共用的方法. 考虑使用个别配置字段 extract 来配置使用何种方式来配置 css-loader. 参见 util.js 代码.
- new MiniCssExtractPlugin({
- filename: 'css/[name].[hash:8].css',
- chunkFilename: 'css/[name]-[id].[hash:8].css',
- }),
生成 HTML
使用 htmlWebpackPlugin, 配合 ejs. 可以使控制 html 的生成. 通过配置的方式, 生成 html. 因为 HtmlWebpackPlugin 本身可以解析 ejs, 所以不需要单独引入 ejs 的 loader.
- new HtmlWebpackPlugin({
- filename: 'index.html',
- template: './src/index.ejs', // 设置目录
- title: 'React Demo',
- inject: true, // true->'head' || false->'body'
- minify: {
- // 删除 Html 注释
- removeComments: true,
- // 去除空格
- collapseWhitespace: true,
- // 去除属性引号
- removeAttributeQuotes: true
- // more options:
- // https://github.com/kangax/html-minifier#options-quick-reference
- },
- // necessary to consistently work with multiple chunks via CommonsChunkPlugin
- chunksSortMode: 'dependency'
- }),
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <meta name="robots" content="noindex, nofollow">
- <title><%= htmlWebpackPlugin.options.title %></title>
- <link rel="shortcut icon" href="/favicon.ico" type="image/x-icon">
- <link rel="icon" href="/favicon.ico" type="image/x-icon">
- <% for (var chunk in htmlWebpackPlugin.files.css) { %>
- <link rel="preload" href="<%= htmlWebpackPlugin.files.css[chunk] %>" as="style">
- <% } %>
- <% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
- <link rel="preload" href="<%= htmlWebpackPlugin.files.chunks[chunk].entry %>" as="script">
- <% } %>
- <base href="/">
- </head>
- <body>
- <div id="root"></div>
- </body>
- <style type="text/css">
- body {
- font-family: 'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif;
- }
- </style>
- </html>
复制静态目录
将所以可能被请求的静态文件, 分别放在 assets 目录下. 那么在打包后, 为了保证目录能正常访问(不使用 CDN 等加载静态资源时), 我们可以配置 publicPath = '/' . 然后借助于 CopyWebpackPlugin 实现资源复制.
- new CopyWebpackPlugin([{
- from: './src/assets/',
- to: 'assets'
- }]),
将 src/assets 复制到 dist/assets 目录下.
开启打包分析
借助插件
BundleAnalyzerPlugin
直接在 plugins 中创建该插件:
- // webpack.prod.conf.js
- const BundleAnalyzerPlugin = process.env.NODE_ENV=== 'analysis' ? require('webpack-bundle-analyzer').BundleAnalyzerPlugin:null
- process.env.NODE_ENV=== 'analysis' ? new BundleAnalyzerPlugin() : ()=>{}
在 package.json 中可做如下配置:
- "scripts": {
- "analysis": "cross-env NODE_ENV=analysis webpack -p --mode production --progress --config ./build/webpack.prod.conf.js",
- },
通过注入环境变量, 来控制是否运行打包分析.
ssh 部署
打包后的 dist 文件夹, 可以直接借助 node 的 ssh-node , 直接部署到服务器指定的目录下. ssh-node 既支持 ssh, 也支持密码登录. 建议可以为在每个项目下, 新建一个. ssh 文件, 存放项目的私钥. 代码如下:
- // usage: https://www.npmjs.com/package/node-ssh
- var path, node_ssh, ssh, fs, opn, host
- fs = require('fs')
- path = require('path')
- node_ssh = require('node-ssh')
- opn = new require('opn')
- ssh = new node_ssh()
- host = 'localhost'
- var localDir = './dist'
- var remoteDir = '/opt/frontend/new'
- var removeCommand = 'rm -rf ./*'
- var pwdCommand = 'pwd'
- ssh.connect({
- host: host,
- username: 'root',
- port: 22,
- // password,
- privateKey: "./.ssh/id_rsa",
- })
- .then(function() {
- ssh.execCommand(removeCommand, { cwd:remoteDir }).then(function(result) {
- console.log('STDOUT:' + result.stdout)
- console.log('STDERR:' + result.stderr)
- ssh.putDirectory(localDir, remoteDir).then(function() {
- console.log("The File thing is done")
- ssh.dispose()
- opn('http://'+host, {app:['chrome']})
- }, function(error) {
- console.log("Something's wrong")
- console.log(error)
- ssh.dispose()
- })
- })
- })
此时, 在命令行直接 node deploy.js 就可以运行以上脚本, 我们也可以添加一个 build + deploy 的 script 脚本, 便于启动.
- "scripts": {
- "depoly": "npm run build && node ./deploy.js",
- }
结语
本次从零到一, 新建了一个 react 脚手架. 过程中有很多问题, 也参考了不少大牛的解释. 代码里也有诸多问题. 还望各位看官, 不吝指教.
记得留下你的足迹哦.
来源: https://www.cnblogs.com/DDante/p/9368694.html