资讯专栏INFORMATION COLUMN

从零到一,新建webpack工程

Code4App / 2700人阅读

摘要:指定启用例如上述代码,就使用和处理了除了以外的。设置当前的为,同样这个配置也可以写在中。设置目录删除注释去除空格去除属性引号复制静态目录将所以可能被请求的静态文件,分别放在目录下。结语本次从零到一,新建了一个脚手架。

react-sample-javascript

为了实现一个可定制化高的react工程,我们往往会自己搭建一个react工程。所以本文会从零开始搭建一个react脚手架工程。解释webpack中配置的含义。
基于webpack 4.0 。包含 开发环境配置,生产环境配置,代码分离,css提取,gzip压缩,base64加载资源,打包分析,ssh一键部署等常用配置。
github项目地址
[TOC]

项目初始化 统一规范代码格式

配置 .editorconfig 使得IDE的方式统一 (见代码)

配置 .eslintrc.js 使得代码规范统一 (见代码)

预期功能

管理资源: 能加载css、sccc、less、以及静态文件

管理输出:将打包后的静态文件输出至static目录下,以各自的文件类型管理

dev:使用source map,方便调试时代码定位

dev:配置devServer,并配置热替换,热加载,自动刷新,自动打开浏览器,并预留proxyTable

dev:设置默认打开8080,被占用则寻找下一个空接口

production:代码分离,打包css文件,css代码压缩,js代码压缩,输出到模板html,配置gzip

analysis::使用BundleAnalyzerPlugin 分析打包后的性能

目录结构
:.
│  .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.basewebpack.devwebpack.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构建多页面 可以参考这篇文章。不过现在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

output

output最基础的两个配置为 pathfilename

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常用的两个配置为 aliasextensions

alias 创建import或者require的别名

extensins 自动解析文件拓展名,补全文件后缀

resolve: {
    // 自动解析文件扩展名(补全文件后缀)(从左->右)
    // import hello from "./hello"  (!hello.js? -> !hello.jsx? -> !hello.json)
    extensions: [".js", ".jsx", ".json"],
    alias: {
      "@": resolve("src")
    }
  },
module

module的选项决定了如何处理项目中的不同类型的模块。其中常用的有 rulesnoParese 两个配置项。

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-lodaerbabel-loader 处理了除了node_modules 以外的 js||jsx。同时配置了,解析图片、视频、字体文件等的解析,当rules匹配到的文件时,小于10000 byte 时,采用url-loader解析文件。

Webpack开发配置

因为在webpack 4.X 中使用了流行的 ”约定大于配置“ 的做法,所以在新加入配置项 mode ,可以告知webpack使用相应模式的内置优化。

选项 描述
development 会将process.env.NODE_ENV 的值设为 development 。启用 NamedChunksPluginNamedMoudulesPlugin
production 会将process.env.NODE_ENV 的值设为 production 。启用 FlagDependencyUsagePluginFlagIncludedChunksPluginModuleConcatenationPluginNoEmitOnErrorsPluginOccurrenceOrderPluginSideEffectsFlagPlugin 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里,设置 contentBasedist 目录。不过此时需要使用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
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"
    }),



  
  
  

  <%= htmlWebpackPlugin.options.title %>
  
  

  <% for (var chunk in htmlWebpackPlugin.files.css) { %>
  
  <% } %>
  <% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
  
  <% } %>

  


复制静态目录

将所以可能被请求的静态文件,分别放在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.ucloud.cn/yun/108085.html

相关文章

  • 前端之从零开始系列

    摘要:只有动手,你才能真的理解作者的构思的巧妙只有动手,你才能真正掌握一门技术持续更新中项目地址求求求源码系列跟一起学如何写函数库中高级前端面试手写代码无敌秘籍如何用不到行代码写一款属于自己的类库原理讲解实现一个对象遵循规范实战手摸手,带你用撸 Do it yourself!!! 只有动手,你才能真的理解作者的构思的巧妙 只有动手,你才能真正掌握一门技术 持续更新中…… 项目地址 https...

    Youngdze 评论0 收藏0
  • 从零到一 styled-components 4.x 的使用

    摘要:废话不多话,来上车安装或者简述使用创建全局的样式首先创建一个文件,例如引全局包里面为项目需要的内容在组件内把引入的当做标签写入创建一个局部的样式引局部包里面为项目需要的内容在组件内把引入的当做标签写入类嵌套类似于用法大同小异列举 废话不多话,来上车! 安装: npm install --save styled-components (或者 yarn add styled-com...

    Yuqi 评论0 收藏0
  • 从零到一,撸一个在线斗地主(上篇)

    摘要:原文从零到一,撸一个在线斗地主上篇作者背景朋友来深圳玩,若说到在深圳有什么好玩的,那当然是宅在家里斗地主了可是天算不如人算,扑克牌丢了几张不全大热天的,谁愿意出去买牌啊。 原文:从零到一,撸一个在线斗地主(上篇) | AlloyTeam作者:TAT.vorshen 背景:朋友来深圳玩,若说到在深圳有什么好玩的,那当然是宅在家里斗地主了!可是天算不如人算,扑克牌丢了几张不全……大热天的,...

    raoyi 评论0 收藏0
  • 从零到一:实现通用一镜到底H5

    摘要:最终针对以上需求,我选用了这几个库来实现通用的一镜到底。你可能会问那怎样实现上面说的几种类型的一镜到底实际上,几种类型的不同只是动画变换方式不一样而已。 showImg(https://segmentfault.com/img/bVbm2Kp?w=900&h=500); 写在前面 整个2018年都被工作支配,文章自17年就断更了,每次看到有消息提示过往的文章被收藏,或者有人点赞,都不胜...

    Crazy_Coder 评论0 收藏0
  • 从零到一:用Phaser.js写意地开发小游戏(Chapter 2 - 搭建游戏的骨架)

    摘要:结束展示游戏最终得分排名等。对象池游戏中生成非常多的元素,我们会需要一个对象池来维护他们,对象池可以理解成是一个。那么关于对象池有以下相关的操作尽可能的复用对象,可以减少内存的开销。 showImg(https://segmentfault.com/img/bVMR3L?w=900&h=500); 写在前面 上一节我们认识了Phaser.js,也说到了Phaser比较适合开发2D的小游...

    yhaolpz 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<