>>建立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 注意这里是本地安装,也可以用全局安装
安装其他可选插件:
>>配置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,可能需要手动修改代码。