ccdat

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

>>建立nodejs工程

新建文件夹,npm init 生成package.json

>>安装webpack 和 webpack-dev-server

npm install --save-dev webpack@3.8.1 注意4.x版本语法有些变化

npm install --save-dev webpack-dev-server@2.9.7 注意踩坑记录1

>>安装babel转码es6

Babel其实是几个模块化的包,其核心功能位于称为babel-core的npm包中,webpack可以把其不同的包整合在一起使用,对于每一个你需要的功能或拓展,你都需要安装单独的包(用得最多的是解析Es6的babel-env-preset包和解析JSX的babel-preset-react包)。

babel 6 与 bable-loader 7匹配,

另外解决promise等,用 babel-polyfill,bebel6对应版本为babel-polyfill 6,解决组件按需加载编译,用 babel-plugin-import

>>支持 react 开发

npm install --save-dev react react-dom 注意这里是本地安装,也可以用全局安装

安装其他可选插件:

html-webpack-plugin
clean-webpack-plugin
copy-webpack-plugin

>>配置webpack.config.js

 

踩坑记录

1:webpack是3.x版本的,webpack-dev-server是3.x的版本,这两个版本不兼容,可以把webpack-dev-server降到2.x版本

 

踩坑解决办法示例:TypeError: Cannot read property 'compile' of undefined #1334

解决思路: 优先使用 Google 引擎进行搜索关键词句, 比如 webpack Cannot read property 'compile' of undefined;看能否找到相应的问题。

如果不行,不妨换一种方式再搜索,譬如:site:stackoverflow.com webpack Cannot read property 'compile' of undefined,在具体某个网站下搜索;如果还是没能找见解决办法的话,可以在各种平台提问,比如 segmentfault。

额外补充: 对于 Google 这个工具还真是有必要先学,具体常用操作可参见:如何更好地运用 Chrome (Google)。倘若,不能够没有适用 Google 的环境,那么这里整理集结若干优质搜索引擎,堪称 Google 搜索优质替代品,可供参考。

 

package.json 示例:

{
  "name": "webpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --config webpack.config.js --watch"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "antd": "^3.25.0",
    "babel-core": "^6.26.3",
    "babel-loader": "^7.1.5",
    "babel-plugin-import": "^1.12.2",
    "babel-plugin-transform-class-properties": "^6.24.1",
    "babel-plugin-transform-runtime": "^6.23.0",
    "babel-polyfill": "^6.26.0",
    "babel-preset-env": "^1.7.0",
    "babel-preset-react": "^6.24.1",
    "babel-preset-stage-0": "^6.24.1",
    "clean-webpack-plugin": "^1.0.0",
    "copy-webpack-plugin": "^4.6.0",
    "css-loader": "^3.2.0",
    "extract-text-webpack-plugin": "^3.0.2",
    "file-loader": "^4.2.0",
    "html-webpack-plugin": "^3.2.0",
    "less-loader": "^5.0.0",
    "sass-loader": "^7.3.1",
    "style-loader": "^1.0.0",
    "uglifyjs-webpack-plugin": "^1.3.0",
    "url-loader": "^2.1.0",
    "webpack": "^3.8.1",
    "webpack-bundle-analyzer": "^3.1.0",
    "webpack-dev-server": "^2.9.7"
  },
  "dependencies": {
    "react": "^16.9.0",
    "react-dom": "^16.9.0"
  }
}

 

webpack.config.js 示例:

const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const CleanWebpackPlugin = require("clean-webpack-plugin");
// const ExtractTextPlugin = require("extract-text-webpack-plugin");
// const UglifyJsPlugin  = require('uglifyjs-webpack-plugin');

module.exports = {
  // context: path.resolve(__dirname), //webpack运行时的根目录,默认为当前目录

  // 配置寻找模块的规则
  resolve: {
    modules: [ // 寻找模块的根目录,array 类型,默认以 node_modules 为根目录
      path.resolve(__dirname),
      'node_modules'
    ],
    extensions: ['.js', '.jsx', '.json', '.css', '.less', '.scss'], // 当模块没有后缀名时,webpack尝试添加后查找文件,常用的放前面
    alias: { // 别名,用于替换import导入模块的路径,例如可在dev环境导入不压缩的包,prd环境导入压缩的包
      "react": "react/cjs/react.production.min.js", // 将import react替换为import "react.min.js"
      "react-dom": "react-dom/cjs/react-dom.production.min.js",
      // "LP": "xxx/lp.min.js", // 将"LP"替换为xxx/lp.min.js
      // 'only-module$': 'new-module'
      // 使用结尾符号 $ 后,只把 'only-module'结尾的路径 映射成 'new-module',
      // 'module/path/file' 不会被映射成 'new-module/path/file'
    },
    enforceExtension: false, // 是否强制导入语句必须要写明文件后缀
  },

  // entry 表示入口文件,注意这里的路径拼上resolve.modules路径即尝试加载入口文件的完整路径
  // 类型可以是 string | object | array   
  // entry: "src/js/entry.js", // 只有1个入口,入口只有1个文件,对应1个chunk出口文件,且命名为main
  // entry: ["src/js/entry1.js", "src/js/entry2.js"], // 只有1个入口,入口有2个文件,对应1个chunk出口文件,且命名为main
  entry: { // 有2个入口,对应2个chunk出口文件。entry为object时对应多个chunk出口文件,名称默认为key值。
    // 'vendor': ['babel-polyfill', 'isomorphic-fetch', 'prop-types', 'react', 'react-dom', 'react-redux', 'react-router-dom', 'redux', 'react-bootstrap'],
    'vendor': ['babel-polyfill', 'react', 'react-dom'],
    "index": "src/app/index.js"
  },
  // entry: ()=>{ // 可由函数动态生成entry
  //   return {
  //     a:"xxx",
  //     b:"yyy"
  //   }
  // }

  // resolveLoader: { // 告诉 webpack 如何寻找Loader源文件,可用于加载本地的Loader
  //   modules: ['node_modules'], // 去哪个目录寻找
  //   extensions: ['.js', 'json'], // 入口文件后缀
  //   mainFields: ['loader', 'main'] // 优先使用哪种类型的入口,可不配置
  // },

  externals: { // 告诉webpack要构建的代码中使用了哪些不用打包的模块(通常直接在模板html的script标签中引入),即直接使用JS运行环境提供的全局变量
    jquery: 'window.jQuery' // 将导入语句里的jqurey替换成全局变量 window.jQuery。例如代码中有 import $ from 'jquery' 会被正确的替换
  }, // 一般代码中也不会用模块引入的语法引入本身不打包的模块,因此这一项也可以不配置

  // 如何输出结果:在 Webpack 经过一系列处理后,如何输出最终想要的代码。
  output: {
    // 输出文件存放的目录,必须是 string 类型的绝对路径。
    path: path.resolve(__dirname, 'dist'),
    // 引用资源的路径 URL 前缀,拼上entry的路径即为资源访问路径。
    // publicPath: 'https://cdn.example.com/', // 放到 CDN 上去,html中引入js时使用https://cdn.example.com/xxx.js
    // 通常本地调试和发布到线上时访问路径不同,可以根据参数化选择:
    // publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath

    // 输出文件的名称
    // filename: 'bundle.js', // 完整的名称
    // filename: '[name].js', // 当配置了多个 entry 时,通过名称模版为不同的 entry 生成不同的文件名称
    filename: '[name].[chunkHash:8].js', // 根据文件内容 hash 值取8位生成文件名称,用于浏览器长时间缓存文件


    // 导出库的名称,string 类型
    // 不填它时,默认输出格式是匿名的立即执行函数
    // library: 'MyLibrary',

    // 导出库的类型,枚举类型,默认是 var
    // 可以是 umd | umd2 | commonjs2 | commonjs | amd | this | var | assign | window | global | jsonp ,
    // libraryTarget: 'umd', 

    // 附加 Chunk 的文件名称,用于指定无入口、动态加载、webpack运行过程中生成的chunk的名称,通常也可以在具体的plugin中去配置
    // chunkFilename: '[id].js',
    // chunkFilename: '[chunkhash].js'
  },

  // 配置模块相关
  module: {
    rules: [ // 配置 Loader
      {
        test: /\.jsx?$/, // 正则匹配命中要使用该 Loader 的文件,可以为字符串或正则,或者数组类型
        // include: [ // 匹配范围包含,即只会命中这里面的文件
        //   path.resolve(__dirname, 'src')
        // ],
        exclude: /node_modules/, // 匹配范围不包含,即排除这里面的文件
        use: [{ // 使用哪些 Loader,有多个时从后往前执行,可以通过options传一些参数
          loader: "babel-loader"
        }],
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'] // 不传参数时可以简写
        // 注:style-loader作用是将css编译到js中,然后运行时插入DOM节点的style属性,可能导致js代码较大,且css复用性不够。可用ExtractTextWebpackPlugin优化。
        // use: ExtractTextPlugin.extract({
        //   fallback: "style-loader", // 编译后用什么loader来提取css文件并引入到html
        //   use: "css-loader"
        // })
      },
      {
        test: /\.less$/,
        use:['style-loader','css-loader', 'less-loader']
      },
      {
        test: /\.scss$/,
        use:['style-loader','css-loader', 'sass-loader']
      },
      {
        // 对小图片或字体文件直接编码为base64提高加载速度,当图片超过限制时直接使用file-loader拷贝
        test: /\.(png|jpe?g|gif|svg|eot|woff2?|ttf|pdf)$/,
        loader: 'url-loader',
        include: [path.resolve(__dirname, 'src')], 
        options: {
          limit: 10000, // 小于10kb的才编码为base64,建议10kb量级的小文件才编码
          name: 'media/[name].[hash:7].[ext]'
          // 另外还可以配置publicPath等参数,参见文末说明
        }
      },
    ],
    noParse: [ // 不用解析和处理的模块,如jQuery等非模块化的库,被忽略的模块不能包含import require define等模块化语句
      /jquery|chartjs/  // 用正则匹配
    ],
    // noParse: (path) => {
    //   return /jquery|chartjs/.test(path);
    // }
  },

  plugins: [
    new CleanWebpackPlugin(['dist']),
    new CopyWebpackPlugin([{
      from: __dirname+'/src/resource', // 静态资源目录地址
      to: __dirname+'/dist/resource',
      ignore: ["test.html"]
    }]),
    // new ExtractTextPlugin({ // 提取js的css到文件,参见文末说明
    //   filename: 'css/style.[contenthash:16].css',
    // }),
    new webpack.optimize.CommonsChunkPlugin({ // 从vendor chunk中提取第三方公共模块,然后从中提取webpack运行文件到runtime
      name: ['vendor','runtime'],
      filename: '[name].[hash:7].js',
      minChunks: 2 // 公共模块被引用几次时被提取出来
    }),
    // new webpack.optimize.CommonsChunkPlugin({ // 提取自定义公共模块
    //     name: 'common',
    //     filename: '[name].[hash:7].js',
    //     chunks: ['first','second']//从first和second chunk中抽取commons chunk
    // }),
    // new UglifyJsPlugin(),
    new HtmlWebpackPlugin({
      title: "首页", // 指定页面标题
      favicon: "", // 指定页面图标
      template: __dirname + "/src/template/index.html", // html模板文件路径
      inject: true, // true|head|body|false,当传入 true或者 body时所有js模块将被放置在body元素的底部,head时则会放在head元素内
      // minify:{    // 压缩HTML文件
      //   removeComments:true,    // 移除HTML中的注释
      //   collapseWhitespace:true    // 删除空白符与换行符
      // },
      filename: "index.html", // 输出html文件路径
      chunks: [ // 向html script标签中添加哪些chunk
        "runtime", // webpack运行文件要排在最前面优先加载
        "vendor",
        "index"        
      ],
      // excludeChunks: [ // 被排除的模块
      //   "main"
      // ]
      chunksSortMode: 'manual', // 设置chunk插入到html的script标签的顺序, none|auto|dependency|manual|{function} 默认为'auto,常用manual根据chunks的位置手动排序
    }),
  ],
  devServer: {
    contentBase: path.join(__dirname, "dist"), //本地服务器所加载的页面所在的目录
    publicPath: "/dist/", // 和output.publicPath作用一样,本地调试用,会覆盖output.publicPath的值,确保以/开头以/结尾
    inline: true, // 实时刷新
    // hot: true, // 是否开启模块热替换功能(不重新加载整个网页的情况下刷新模块),默认关闭
    port: 9000, // 端口改为9000
    compress: true, // 是否开启 gzip 压缩
    https: false, // 是否开启 HTTPS 模式
    // open:true // 自动打开浏览器
    // headers: { // response中注入一些响应头
    //   'X-foo': '112233'
    // },
    // historyApiFallback: true, // 404时定向跳转,一应用在HTML5 History API 的单页应用,比如访问不到路由时跳转到index.html
    // 还可以配置proxy 实现跨域请求,参见文末
  },
  // http://localhost:9000/webpack-dev-server 可以查看运行时dev-server在内存中生成的目录结构

  // 其他配置
  devtool: 'source-map', // 配置 source-map 类型,方便调试时查看源码
  profile: false, // 是否捕捉 Webpack 构建的性能信息,用于分析什么原因导致构建性能不佳
  cache: true, // 是否启用缓存提升构建速度
  watch: true, // 是否开启监听模式,在文件变化时自动重新编译(默认否,当使用devServer时默认开启)
  watchOptions: { // 监听模式选项
    // 不监听的文件或文件夹,支持正则匹配。默认为空
    ignored: /node_modules/,
    // 监听到变化发生后会等待多长时间再去执行编译,防止文件更新太快导致重新编译频率太高,默认为300ms 
    aggregateTimeout: 300,
    // 检测文件是否发生变化的间隔时长,默认每隔1000毫秒询问一次
    poll: 1000
  },
  
  // 输出文件性能检查配置
  performance: { 
    hints: 'warning', // 有性能问题时输出警告
    hints: 'error', // 有性能问题时输出错误
    hints: false, // 关闭性能检查
    maxAssetSize: 200000, // 最大文件大小 (单位 bytes)
    maxEntrypointSize: 400000, // 最大入口文件大小 (单位 bytes)
    assetFilter: function(assetFilename) { // 过滤要检查的文件
      return assetFilename.endsWith('.css') || assetFilename.endsWith('.js');
    }
  },

  stats: { // 控制台输出日志控制
    assets: true,
    colors: true,
    errors: true,
    errorDetails: true,
    hash: true,
  }
  
};


// node中的路径
// __dirname: 总是返回被执行的 js 所在文件夹的绝对路径
// __filename: 总是返回被执行的 js 的绝对路径
// process.cwd(): 总是返回运行 node 命令时所在的文件夹的绝对路径

/*
devServer proxy 实现跨域

有时候我们使用webpack在本地启动服务器的时候,由于我们使用的访问的域名是 http://localhost:8081 这样的,但是我们服务端的接口是其他的,
那么就存在域名或端口号跨域的情况下,但是很幸运的是 devServer有一个叫proxy配置项,可以通过该配置来解决跨域的问题,那是因为 dev-server 使用了 http-proxy-middleware 包(了解该包的更多用法 )。
假如现在我们本地访问的域名是 http://localhost:8081, 但是我现在调用的是百度页面中的一个接口,该接口地址是:http://news.baidu.com/widget?ajax=json&id=ad。现在我们只需要在devServer中的proxy的配置就可以了:
如下配置:
proxy: {
  '/api': {
    target: 'http://news.baidu.com', // 目标接口的域名
    // secure: true,  // https 的时候 使用该参数
    changeOrigin: true,  // 是否跨域
    pathRewrite: {
      '^/api' : ''  // 重写路径
    }
  }
}
然后我们在main.js里面编写如下代码:
import axios from 'axios';
axios.get('/api/widget?ajax=json&id=ad').then(res => {
  console.log(res);
});
在这里请求我使用 axios 插件,其实和jquery是一个意思的。为了方便就用了这个。
下面我们来理解下上面配置的含义:
1. 首先是百度的接口地址是这样的:http://news.baidu.com/widget?ajax=json&id=ad;
2. proxy 的配置项 '/api' 和 target: 'http://news.baidu.com' 的含义是,匹配请求中 /api 含有这样的域名重定向 到 'http://news.baidu.com'来。
因此我在接口地址上 添加了前缀 '/api', 如: axios.get('/api/widget?ajax=json&id=ad'); 因此会自动补充前缀,也就是说,url: '/api/widget?ajax=json&id=ad' 等价
于 url: 'http://news.baidu.com/api/widget?ajax=json&id=ad'.
3. changeOrigin: true/false 还参数值是一个布尔值,含义是 是否需要跨域。
4. secure: true, 如果是https请求就需要改参数配置,需要ssl证书吧。
5. pathRewrite: {'^/api' : ''}的含义是重写url地址,把url的地址里面含有 '/api' 这样的 替换成 '', 
因此接口地址就变成了 http://news.baidu.com/widget?ajax=json&id=ad; 因此就可以请求得到了,最后就返回
接口数据了。
*/

/* https://segmentfault.com/a/1190000018987483?utm_source=tag-newest
url-loader 会将引入的图片(还有字体、音视频等)编码,生成dataURl并将其打包到文件中,引入这个dataURL就能访问。如果图片较大,编码会消耗性能。
因此url-loader提供了一个limit参数,小于limit字节的文件会被转为DataURl,大于limit的内部调用file-loader进行copy。
接下来摘取几个重要的属性做说明

outputPath
该属性指明我们最终导出的文件路径
最终导出的文件路径 === output.path + url-loader.outputPath + url-loader.name

publicPath(常用于生成环境)
该属性指明我们最终引用的文件路径(打包生成的index.html文件里面引用资源的前缀)
最终引用的文件路径前缀 === output.publicPath + url-loader.publicPath + url-loader.name

name
该属性指明文件的最终名称。
同样的,我们可以直接把outputPath的内容写到name中,一样可以生成对应的路径
经过上面的说明,我们得出结论,最终的静态文件路径(图片,音频,视频,字体等)=== output.publicPath + url-loader.publicPath + output.path + url-loader.outputPath + url-loader.name

我们在本地开发的时候都是localhost:8080/下面的根目录,所以当图片生成如下的绝对地址是不会出问题的,可是同样的webpack配置放到生成环境上就不一定了。
因为生成环境大部分的前端静态文件都不是在根目录啊,有可能就是这样的目录结构
www/
 +folder/
   +static/
     +css/
     +img/
     +js/
   +index.html
这样你开发环境的绝对路径放到服务器上面自然就404了,所以要不然用相对路径,要不然就统一写死绝对路径(针对不同环境添加不同的publicPath)
(同样道理,解释为什么css里面的背景图路径404,但是这个解决起来需要用到extract-text-webpack-plugin或者mini-css-extract-plugin这个插件,前者用于webpack4以下版本,后者是4以上版本,配置options里面的publicPath)

css 提取插件,`extract-text-webpack-plugin` 插件 或者 `mini-css-extract-plugin` 的 loader也都提供了publicPath,用来复写提取出来的css文件中的资源的引入路径
*/

 

 

模板html示例:

<!DOCTYPE html>
<head>
    <title><%=htmlWebpackPlugin.options.title %></title>
    <meta charset="utf-8" />
    <style>
        /* 垂直居中显示 */
        .center-in-center{
            position: absolute;
            margin: 10px;
            padding: 10px;
            top: 50%;
            left: 50%;
            -webkit-transform: translate(-50%, -50%);
            -moz-transform: translate(-50%, -50%);
            -ms-transform: translate(-50%, -50%);
            -o-transform: translate(-50%, -50%);
            transform: translate(-50%, -50%);
        }
    </style>
</head>
<body>
    <div id="root" >
    </div>
</body>
</html>

解释下版本号:

1.15.2对应就是MAJOR,MINOR.PATCH:1是marjor version;15是minor version;2是patch version。
MAJOR:这个版本号变化了表示有了一个不可以和上个版本兼容的大更改。
MINOR:这个版本号变化了表示有了增加了新的功能,并且可以向后兼容。
PATCH:这个版本号变化了表示修复了bug,并且可以向后兼容。

当我们使用最新的Node运行‘npm instal --save xxx',的时候,他会优先考虑使用插入符号(^)而不是波浪符号(~)了。

波浪符号(~):他会更新到当前minor version(也就是中间的那位数字)中最新的版本。放到我们的例子中就是:body-parser:~1.15.2,这个库会去匹配更新到1.15.x的最新版本,如果出了一个新的版本为1.16.0,则不会自动升级。波浪符号是曾经npm安装时候的默认符号,现在已经变为了插入符号。

插入符号(^):这个符号就显得非常的灵活了,他将会把当前库的版本更新到当前major version(也就是第一位数字)中最新的版本。放到我们的例子中就是:bluebird:^3.3.4,这个库会去匹配3.x.x中最新的版本,但是他不会自动更新到4.0.0。

因为major version变化表示可能会影响之前版本的兼容性,所以无论是波浪符号还是插入符号都不会自动去修改major version,因为这可能导致程序crush,可能需要手动修改代码。

 

const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const CleanWebpackPlugin = require("clean-webpack-plugin");
// const ExtractTextPlugin = require("extract-text-webpack-plugin");
// const UglifyJsPlugin  = require('uglifyjs-webpack-plugin');

module.exports = {
  // context: path.resolve(__dirname), //webpack运行时的根目录,默认为当前目录

  // 配置寻找模块的规则
  resolve: {
    modules: [ // 寻找模块的根目录,array 类型,默认以 node_modules 为根目录
      path.resolve(__dirname),
      'node_modules'
    ],
    extensions: ['.js''.jsx''.json''.css''.less''.scss'], // 当模块没有后缀名时,webpack尝试添加后查找文件,常用的放前面
    alias: { // 别名,用于替换import导入模块的路径,例如可在dev环境导入不压缩的包,prd环境导入压缩的包
      "react": "react/cjs/react.production.min.js"// 将import react替换为import "react.min.js"
      "react-dom": "react-dom/cjs/react-dom.production.min.js",
      // "LP": "xxx/lp.min.js", // 将"LP"替换为xxx/lp.min.js
      // 'only-module$': 'new-module'
      // 使用结尾符号 $ 后,只把 'only-module'结尾的路径 映射成 'new-module',
      // 'module/path/file' 不会被映射成 'new-module/path/file'
    },
    enforceExtension: false// 是否强制导入语句必须要写明文件后缀
  },

  // entry 表示入口文件,注意这里的路径拼上resolve.modules路径即尝试加载入口文件的完整路径
  // 类型可以是 string | object | array   
  // entry: "src/js/entry.js", // 只有1个入口,入口只有1个文件,对应1个chunk出口文件,且命名为main
  // entry: ["src/js/entry1.js", "src/js/entry2.js"], // 只有1个入口,入口有2个文件,对应1个chunk出口文件,且命名为main
  entry: { // 有2个入口,对应2个chunk出口文件。entry为object时对应多个chunk出口文件,名称默认为key值。
    // 'vendor': ['babel-polyfill', 'isomorphic-fetch', 'prop-types', 'react', 'react-dom', 'react-redux', 'react-router-dom', 'redux', 'react-bootstrap'],
    'vendor': ['babel-polyfill''react''react-dom'],
    "index": "src/app/index.js"
  },
  // entry: ()=>{ // 可由函数动态生成entry
  //   return {
  //     a:"xxx",
  //     b:"yyy"
  //   }
  // }

  // resolveLoader: { // 告诉 webpack 如何寻找Loader源文件,可用于加载本地的Loader
  //   modules: ['node_modules'], // 去哪个目录寻找
  //   extensions: ['.js', 'json'], // 入口文件后缀
  //   mainFields: ['loader', 'main'] // 优先使用哪种类型的入口,可不配置
  // },

  externals: { // 告诉webpack要构建的代码中使用了哪些不用打包的模块(通常直接在模板html的script标签中引入),即直接使用JS运行环境提供的全局变量
    jquery: 'window.jQuery' // 将导入语句里的jqurey替换成全局变量 window.jQuery。例如代码中有 import $ from 'jquery' 会被正确的替换
  }, // 一般代码中也不会用模块引入的语法引入本身不打包的模块,因此这一项也可以不配置

  // 如何输出结果:在 Webpack 经过一系列处理后,如何输出最终想要的代码。
  output: {
    // 输出文件存放的目录,必须是 string 类型的绝对路径。
    path: path.resolve(__dirname'dist'),
    // 引用资源的路径 URL 前缀,拼上entry的路径即为资源访问路径。
    // publicPath: 'https://cdn.example.com/', // 放到 CDN 上去,html中引入js时使用https://cdn.example.com/xxx.js
    // 通常本地调试和发布到线上时访问路径不同,可以根据参数化选择:
    // publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath

    // 输出文件的名称
    // filename: 'bundle.js', // 完整的名称
    // filename: '[name].js', // 当配置了多个 entry 时,通过名称模版为不同的 entry 生成不同的文件名称
    filename: '[name].[chunkHash:8].js'// 根据文件内容 hash 值取8位生成文件名称,用于浏览器长时间缓存文件


    // 导出库的名称,string 类型
    // 不填它时,默认输出格式是匿名的立即执行函数
    // library: 'MyLibrary',

    // 导出库的类型,枚举类型,默认是 var
    // 可以是 umd | umd2 | commonjs2 | commonjs | amd | this | var | assign | window | global | jsonp ,
    // libraryTarget: 'umd', 

    // 附加 Chunk 的文件名称,用于指定无入口、动态加载、webpack运行过程中生成的chunk的名称,通常也可以在具体的plugin中去配置
    // chunkFilename: '[id].js',
    // chunkFilename: '[chunkhash].js'
  },

  // 配置模块相关
  module: {
    rules: [ // 配置 Loader
      {
        test: /\.jsx?$/// 正则匹配命中要使用该 Loader 的文件,可以为字符串或正则,或者数组类型
        // include: [ // 匹配范围包含,即只会命中这里面的文件
        //   path.resolve(__dirname, 'src')
        // ],
        exclude: /node_modules/// 匹配范围不包含,即排除这里面的文件
        use: [{ // 使用哪些 Loader,有多个时从后往前执行,可以通过options传一些参数
          loader: "babel-loader"
        }],
      },
      {
        test: /\.css$/,
        use: ['style-loader''css-loader'// 不传参数时可以简写
        // 注:style-loader作用是将css编译到js中,然后运行时插入DOM节点的style属性,可能导致js代码较大,且css复用性不够。可用ExtractTextWebpackPlugin优化。
        // use: ExtractTextPlugin.extract({
        //   fallback: "style-loader", // 编译后用什么loader来提取css文件并引入到html
        //   use: "css-loader"
        // })
      },
      {
        test: /\.less$/,
        use:['style-loader','css-loader''less-loader']
      },
      {
        test: /\.scss$/,
        use:['style-loader','css-loader''sass-loader']
      },
      {
        // 对小图片或字体文件直接编码为base64提高加载速度,当图片超过限制时直接使用file-loader拷贝
        test: /\.(png|jpe?g|gif|svg|eot|woff2?|ttf|pdf)$/,
        loader: 'url-loader',
        include: [path.resolve(__dirname'src')], 
        options: {
          limit: 10000// 小于10kb的才编码为base64,建议10kb量级的小文件才编码
          name: 'media/[name].[hash:7].[ext]'
          // 另外还可以配置publicPath等参数,参见文末说明
        }
      },
    ],
    noParse: [ // 不用解析和处理的模块,如jQuery等非模块化的库,被忽略的模块不能包含import require define等模块化语句
      /jquery|chartjs/  // 用正则匹配
    ],
    // noParse: (path) => {
    //   return /jquery|chartjs/.test(path);
    // }
  },

  plugins: [
    new CleanWebpackPlugin(['dist']),
    new CopyWebpackPlugin([{
      from: __dirname+'/src/resource'// 静态资源目录地址
      to: __dirname+'/dist/resource',
      ignore: ["test.html"]
    }]),
    // new ExtractTextPlugin({ // 提取js的css到文件,参见文末说明
    //   filename: 'css/style.[contenthash:16].css',
    // }),
    new webpack.optimize.CommonsChunkPlugin({ // 从vendor chunk中提取第三方公共模块,然后从中提取webpack运行文件到runtime
      name: ['vendor','runtime'],
      filename: '[name].[hash:7].js',
      minChunks: 2 // 公共模块被引用几次时被提取出来
    }),
    // new webpack.optimize.CommonsChunkPlugin({ // 提取自定义公共模块
    //     name: 'common',
    //     filename: '[name].[hash:7].js',
    //     chunks: ['first','second']//从first和second chunk中抽取commons chunk
    // }),
    // new UglifyJsPlugin(),
    new HtmlWebpackPlugin({
      title: "首页"// 指定页面标题
      favicon: ""// 指定页面图标
      template: __dirname + "/src/template/index.html"// html模板文件路径
      inject: true// true|head|body|false,当传入 true或者 body时所有js模块将被放置在body元素的底部,head时则会放在head元素内
      // minify:{    // 压缩HTML文件
      //   removeComments:true,    // 移除HTML中的注释
      //   collapseWhitespace:true    // 删除空白符与换行符
      // },
      filename: "index.html"// 输出html文件路径
      chunks: [ // 向html script标签中添加哪些chunk
        "runtime"// webpack运行文件要排在最前面优先加载
        "vendor",
        "index"        
      ],
      // excludeChunks: [ // 被排除的模块
      //   "main"
      // ]
      chunksSortMode: 'manual'// 设置chunk插入到html的script标签的顺序, none|auto|dependency|manual|{function} 默认为'auto,常用manual根据chunks的位置手动排序
    }),
  ],
  devServer: {
    contentBase: path.join(__dirname"dist"), //本地服务器所加载的页面所在的目录
    publicPath: "/dist/"// 和output.publicPath作用一样,本地调试用,会覆盖output.publicPath的值,确保以/开头以/结尾
    inline: true// 实时刷新
    // hot: true, // 是否开启模块热替换功能(不重新加载整个网页的情况下刷新模块),默认关闭
    port: 9000// 端口改为9000
    compress: true// 是否开启 gzip 压缩
    https: false// 是否开启 HTTPS 模式
    // open:true // 自动打开浏览器
    // headers: { // response中注入一些响应头
    //   'X-foo': '112233'
    // },
    // historyApiFallback: true, // 404时定向跳转,一应用在HTML5 History API 的单页应用,比如访问不到路由时跳转到index.html
    // 还可以配置proxy 实现跨域请求,参见文末
  },
  // http://localhost:9000/webpack-dev-server 可以查看运行时dev-server在内存中生成的目录结构

  // 其他配置
  devtool: 'source-map'// 配置 source-map 类型,方便调试时查看源码
  profile: false// 是否捕捉 Webpack 构建的性能信息,用于分析什么原因导致构建性能不佳
  cache: false// 是否启用缓存提升构建速度
  watch: true// 是否开启监听模式,在文件变化时自动重新编译(默认否,当使用devServer时默认开启)
  watchOptions: { // 监听模式选项
    // 不监听的文件或文件夹,支持正则匹配。默认为空
    ignored: /node_modules/,
    // 监听到变化发生后会等待多长时间再去执行编译,防止文件更新太快导致重新编译频率太高,默认为300ms 
    aggregateTimeout: 300,
    // 检测文件是否发生变化的间隔时长,默认每隔1000毫秒询问一次
    poll: 1000
  },
  
  // 输出文件性能检查配置
  performance: { 
    hints: 'warning'// 有性能问题时输出警告
    hints: 'error'// 有性能问题时输出错误
    hints: false// 关闭性能检查
    maxAssetSize: 200000// 最大文件大小 (单位 bytes)
    maxEntrypointSize: 400000// 最大入口文件大小 (单位 bytes)
    assetFilter: function(assetFilename) { // 过滤要检查的文件
      return assetFilename.endsWith('.css') || assetFilename.endsWith('.js');
    }
  },

  stats: { // 控制台输出日志控制
    assets: true,
    colors: true,
    errors: true,
    errorDetails: true,
    hash: true,
  }
  
};


// node中的路径
// __dirname: 总是返回被执行的 js 所在文件夹的绝对路径
// __filename: 总是返回被执行的 js 的绝对路径
// process.cwd(): 总是返回运行 node 命令时所在的文件夹的绝对路径

/*
devServer proxy 实现跨域

有时候我们使用webpack在本地启动服务器的时候,由于我们使用的访问的域名是 http://localhost:8081 这样的,但是我们服务端的接口是其他的,
那么就存在域名或端口号跨域的情况下,但是很幸运的是 devServer有一个叫proxy配置项,可以通过该配置来解决跨域的问题,那是因为 dev-server 使用了 http-proxy-middleware 包(了解该包的更多用法 )。
假如现在我们本地访问的域名是 http://localhost:8081, 但是我现在调用的是百度页面中的一个接口,该接口地址是:http://news.baidu.com/widget?ajax=json&id=ad。现在我们只需要在devServer中的proxy的配置就可以了:
如下配置:
proxy: {
  '/api': {
    target: 'http://news.baidu.com', // 目标接口的域名
    // secure: true,  // https 的时候 使用该参数
    changeOrigin: true,  // 是否跨域
    pathRewrite: {
      '^/api' : ''  // 重写路径
    }
  }
}
然后我们在main.js里面编写如下代码:
import axios from 'axios';
axios.get('/api/widget?ajax=json&id=ad').then(res => {
  console.log(res);
});
在这里请求我使用 axios 插件,其实和jquery是一个意思的。为了方便就用了这个。
下面我们来理解下上面配置的含义:
1. 首先是百度的接口地址是这样的:http://news.baidu.com/widget?ajax=json&id=ad;
2. proxy 的配置项 '/api' 和 target: 'http://news.baidu.com' 的含义是,匹配请求中 /api 含有这样的域名重定向 到 'http://news.baidu.com'来。
因此我在接口地址上 添加了前缀 '/api', 如: axios.get('/api/widget?ajax=json&id=ad'); 因此会自动补充前缀,也就是说,url: '/api/widget?ajax=json&id=ad' 等价
于 url: 'http://news.baidu.com/api/widget?ajax=json&id=ad'.
3. changeOrigin: true/false 还参数值是一个布尔值,含义是 是否需要跨域。
4. secure: true, 如果是https请求就需要改参数配置,需要ssl证书吧。
5. pathRewrite: {'^/api' : ''}的含义是重写url地址,把url的地址里面含有 '/api' 这样的 替换成 '', 
因此接口地址就变成了 http://news.baidu.com/widget?ajax=json&id=ad; 因此就可以请求得到了,最后就返回
接口数据了。
*/

/* https://segmentfault.com/a/1190000018987483?utm_source=tag-newest
url-loader 会将引入的图片(还有字体、音视频等)编码,生成dataURl并将其打包到文件中,引入这个dataURL就能访问。如果图片较大,编码会消耗性能。
因此url-loader提供了一个limit参数,小于limit字节的文件会被转为DataURl,大于limit的内部调用file-loader进行copy。
接下来摘取几个重要的属性做说明

outputPath
该属性指明我们最终导出的文件路径
最终导出的文件路径 === output.path + url-loader.outputPath + url-loader.name

publicPath(常用于生成环境)
该属性指明我们最终引用的文件路径(打包生成的index.html文件里面引用资源的前缀)
最终引用的文件路径前缀 === output.publicPath + url-loader.publicPath + url-loader.name

name
该属性指明文件的最终名称。
同样的,我们可以直接把outputPath的内容写到name中,一样可以生成对应的路径
经过上面的说明,我们得出结论,最终的静态文件路径(图片,音频,视频,字体等)=== output.publicPath + url-loader.publicPath + output.path + url-loader.outputPath + url-loader.name

我们在本地开发的时候都是localhost:8080/下面的根目录,所以当图片生成如下的绝对地址是不会出问题的,可是同样的webpack配置放到生成环境上就不一定了。
因为生成环境大部分的前端静态文件都不是在根目录啊,有可能就是这样的目录结构
www/
 +folder/
   +static/
     +css/
     +img/
     +js/
   +index.html
这样你开发环境的绝对路径放到服务器上面自然就404了,所以要不然用相对路径,要不然就统一写死绝对路径(针对不同环境添加不同的publicPath)
(同样道理,解释为什么css里面的背景图路径404,但是这个解决起来需要用到extract-text-webpack-plugin或者mini-css-extract-plugin这个插件,前者用于webpack4以下版本,后者是4以上版本,配置options里面的publicPath)

css 提取插件,`extract-text-webpack-plugin` 插件 或者 `mini-css-extract-plugin` 的 loader也都提供了publicPath,用来复写提取出来的css文件中的资源的引入路径
*/
posted on 2018-12-28 21:05  ccdat  阅读(240)  评论(0编辑  收藏  举报